| name | themed-table-2 |
|---|---|
| description | Use ThemedTable2<T> in a layrz Flutter widget. Apply when displaying a list of domain objects in a table with sort, search, multiselect, and row actions. |
Dart syntax: This library requires Dart ≥ 3.10. Use dot shorthand for all enum values — never write the fully-qualified form.
Full constructor and property reference: read
references/api.mdin this skill's directory.
- Displaying lists of domain objects in tabular form (assets, users, reports, etc.)
- Handles small and large datasets — tested up to 55,000+ rows via virtualized rendering
- Use
ThemedColumn2<T>to define columns alongsideThemedTable2<T>
ThemedTable2<Asset>(
items: store.assets,
actionsCount: 0,
hasMultiselect: false,
columns: [
ThemedColumn2<Asset>(
headerText: 'Name',
valueBuilder: (item) => item.name,
),
ThemedColumn2<Asset>(
headerText: 'Plate',
valueBuilder: (item) => item.plate ?? 'N/A',
),
],
)Always wrap in Expanded or give a fixed height — ThemedTable2 requires bounded height.
- Virtualized via
ListView.builder+ fixeditemExtent— only visible rows are rendered. - Sort runs in a background isolate via
compute()— valueBuilder and customSort must be isolate-safe (no BuildContext, no i18n, no Flutter objects). - Search is debounced 600ms and searches all column values.
actionsCountmust equal the number of buttonsactionsBuilderactually returns — assert enforced.hasMultiselect: truerequires at least one entry inmultiselectActions— assert enforced.onTapDefaultBehaviordefaults to.copyToClipboard; set to.noneto disable.onFilteredCountChangedfires after every_filterAndSortcycle (initial load, search, sort,itemsupdate) with the visible row count. Fires with0for an empty dataset. Optional —nullby default, no overhead when omitted.
valueBuilder and customSort run inside a background isolate. They cannot capture Flutter objects (BuildContext, i18n, State, Streams, widgets). Doing so causes a runtime crash:
Invalid argument(s): Illegal argument in isolate message: object is unsendable
Wrong:
// ❌ CRASH — i18n captures BuildContext
valueBuilder: (item) => i18n.t('status.${item.status}'),Right:
// ✅ SAFE — precompute a plain Map before the ThemedTable2 call
final Map<String, String> statusLabels = {
'active': i18n.t('status.active'),
'inactive': i18n.t('status.inactive'),
};
valueBuilder: (item) => statusLabels[item.status] ?? item.status ?? 'N/A',// With search + actions
ThemedTable2<Asset>(
items: _items,
canSearch: true,
actionsCount: 2,
hasMultiselect: false,
columns: [
ThemedColumn2<Asset>(
headerText: i18n.t('asset.name'),
valueBuilder: (item) => item.name,
),
ThemedColumn2<Asset>(
headerText: i18n.t('asset.plate'),
width: 150,
valueBuilder: (item) => item.plate ?? 'N/A',
),
],
actionsBuilder: (item) => [
ThemedActionButton.edit(
labelText: i18n.t('actions.edit'),
onTap: () => _onEdit(item),
),
ThemedActionButton.delete(
labelText: i18n.t('actions.delete'),
onTap: () => _onDelete(item),
),
],
)
// With multiselect
final ValueNotifier<List<Asset>> _selected = ValueNotifier([]);
ThemedTable2<Asset>(
items: _items,
hasMultiselect: true,
actionsCount: 0,
multiselectValue: _selected,
multiselectActions: [
ThemedActionButton(
icon: LayrzIcons.solarOutlineTrashBin,
labelText: i18n.t('actions.deleteSelected'),
color: Colors.red,
onTap: () => _onDeleteSelected(_selected.value),
),
],
columns: [ /* ... */ ],
)
// With filtered count callback
ThemedTable2<Asset>(
items: _items,
canSearch: true,
actionsCount: 0,
hasMultiselect: false,
onFilteredCountChanged: (count) {
setState(() => _visibleCount = count);
},
columns: [ /* ... */ ],
)
// With programmatic controller
final _controller = ThemedTable2Controller<Asset>();
// In dispose():
_controller.dispose();
ThemedTable2<Asset>(
items: _items,
controller: _controller,
columns: [ /* ... */ ],
actionsCount: 0,
hasMultiselect: false,
)
// Trigger sort programmatically
_controller.sort(columnIndex: 0, ascending: true);
_controller.refresh();