tree_data_table 0.0.1-beta.1 copy "tree_data_table: ^0.0.1-beta.1" to clipboard
tree_data_table: ^0.0.1-beta.1 copied to clipboard

A Flutter widget for displaying hierarchical data in a table with pagination and pinned columns.

Tree Data Table #

A flexible Flutter data table for flat and hierarchical data, with expandable rows, pagination, resizable columns, and pinned columns.

Pub Version Build License: MIT

Beta: The API is still evolving and may introduce breaking changes before the first stable release. Test the package carefully before using it in production.

Features #

  • Flat tables and expandable tree rows of arbitrary depth
  • Fixed-width and flex columns with minimum and maximum widths
  • Drag-to-resize and double-click best-fit column sizing
  • Columns pinned to either edge, including interactive pinning from the header menu
  • Column visibility controls and session-scoped column state persistence
  • Optional root-row pagination
  • Lazy/server-side root pagination and on-demand child loading
  • Single-level and grouped, multi-level headers
  • Light and dark theme support

Installation #

Add the package to your Flutter project:

flutter pub add tree_data_table

Then import it:

import 'package:tree_data_table/tree_data_table.dart';

Localization #

The built-in controls support 16 locales:

  • Arabic (ar), Czech (cs), Dutch (nl), English (en)
  • French (fr), German (de), Italian (it), Japanese (ja)
  • Korean (ko), Polish (pl), Portuguese (pt), Russian (ru)
  • Simplified Chinese (zh), Spanish (es), Turkish (tr), Ukrainian (uk)

Register the package delegates and supported locales on your app to enable the translations. Flutter will select the matching locale from the device or app configuration:

MaterialApp(
  localizationsDelegates: TreeDataTableLocalizations.localizationsDelegates,
  supportedLocales: TreeDataTableLocalizations.supportedLocales,
  home: const MyHomePage(),
)

Quick start #

TreeDataTable must receive bounded height because it expands to fill the available vertical space. A SizedBox or an Expanded widget is usually the simplest choice.

SizedBox(
  height: 420,
  child: TreeDataTable.eager(
    headerRows: [
      HeaderRow(
        columns: [
          HeaderColumn(
            widget: const Text('Name'),
            textVisibilityDialog: 'Name',
            flex: 2,
            minimumWidth: 160,
            pinLeft: true,
          ),
          HeaderColumn(
            widget: const Text('Role'),
            textVisibilityDialog: 'Role',
            flex: 1,
          ),
        ],
      ),
    ],
    dataSource: TreeDataTableEagerDataSource(
      rows: [
        TreeRowNode(
          isExpanded: true,
          columns: [
            TreeColumn(widget: const Text('Engineering')),
            TreeColumn(widget: const Text('Team')),
          ],
          rows: [
            TreeRowNode(
              columns: [
                TreeColumn(widget: const Text('Ada')),
                TreeColumn(widget: const Text('Developer')),
              ],
            ),
          ],
        ),
      ],
      rowsPerPage: const [10, 25, 50],
      rowsPerPageIndex: 0,
    ),
    persistColumnStateId: 'people-table',
  ),
)

Every HeaderColumn requires either width or flex. Each data row should contain the same number of TreeColumn values as the leaf header row.

Configuration #

Choose TreeDataTable.eager for rows already available in memory and TreeDataTable.lazy for rows loaded page by page. Both constructors share these table options:

Option Purpose
headerRows Defines the columns and optional grouped header levels.
dataSource A TreeDataTableEagerDataSource for .eager or a TreeDataTableLazyDataSource for .lazy.
headerPinned Keeps the header visible while rows scroll. Defaults to true.
headerMinExtent / headerMaxExtent Sets the header area's minimum and maximum height.
persistColumnStateId Restores widths, visibility, and pinning for this table during the current app session.
scaleColumnsToFit Recalculates untouched flex columns when the table width changes.

Data-source options control the rows and pagination:

Data source Options
TreeDataTableEagerDataSource rows, optional rowsPerPage, rowsPerPageIndex, and maxLevel
TreeDataTableLazyDataSource pageLoader, rowsPerPage, and optional childrenLoader, loadingBuilder, errorBuilder, rowsPerPageIndex, and maxLevel

Column sizing #

Give a HeaderColumn a fixed width or a flex factor. You can constrain either approach with minimumWidth and maximumWidth: When both are supplied, width takes precedence.

HeaderColumn(
  widget: const Text('Description'),
  textVisibilityDialog: 'Description',
  flex: 2,
  minimumWidth: 180,
  maximumWidth: 480,
)

Users can drag a resizable column separator to adjust its width. Set resizable: false to disable manual resizing for a column.

Set scaleColumnsToFit: true on TreeDataTable to let flex columns follow the available table width. Once a user drags or best-fits a column, that column keeps its selected width while the remaining untouched flex columns continue to use the available space.

Best-fit column sizing #

Double-click a resizable column separator to fit the column to its header and visible rows. Common widgets such as Text, icons, padding, and simple rows are measured without mounting duplicate widgets.

For a custom cell widget, provide its preferred content width:

TreeColumn(
  widget: CustomerStatusWidget(customer),
  autoSizeWidth: 148,
)

You can also compute the value from the current BuildContext with autoSizeWidthBuilder. Configure best-fit behavior on the corresponding header:

HeaderColumn(
  widget: const Text('Status'),
  textVisibilityDialog: 'Status',
  width: 120,
  autoSize: const ColumnAutoSize(
    range: ColumnAutoSizeRange.loadedRows,
    sampleLimit: 500,
    additionalPadding: 8,
  ),
)

By default, best fit considers the header and up to 200 visible rows. Use ColumnAutoSizeRange.loadedRows to sample rows loaded for the current page, set includeHeader: false to ignore the header, or set autoSize: null to disable best fit. The result includes cell padding and tree indentation, then respects minimumWidth and maximumWidth.

Pagination #

Pagination applies to root rows; child rows stay with their parent. The two data source classes make the loading contract explicit.

For a normal table, supply all rows up front. Pagination is optional:

TreeDataTable.eager(
  headerRows: headerRows,
  dataSource: TreeDataTableEagerDataSource(
    rows: rows,
    rowsPerPage: const [25, 50, 100],
  ),
)

Omit rowsPerPage to display every eager root row on one page. Page sizes must be unique positive integers.

Sorting #

Sorting is opt-in for each leaf column. Set sortable: true on its HeaderColumn and provide a sortValue for every matching TreeColumn:

HeaderColumn(
  widget: const Text('Name'),
  textVisibilityDialog: 'Name',
  width: 200,
  sortable: true,
)

TreeColumn(
  widget: Text(customer.name),
  sortValue: customer.name,
)

Pressing the header cycles through ascending, descending, and unsorted. The column menu hides the currently active direction and shows unsort only while that column is sorted. Sorting keeps equal values in their original order and sorts children within their own sibling group. Supply sortComparator on HeaderColumn for non-Comparable values or custom ordering. Eager tables sort the complete data set before pagination; lazy tables sort the currently loaded page.

For large or remote datasets, use TreeDataTable.lazy. Its page loader receives the zero-based page index and selected page size, and returns only that page plus the total number of root rows:

TreeDataTable.lazy(
  headerRows: headerRows,
  dataSource: TreeDataTableLazyDataSource(
    rowsPerPage: const [25, 50, 100],
    maxLevel: 3,
    pageLoader: (pageIndex, pageSize) async {
      final result = await api.fetchRows(
        offset: pageIndex * pageSize,
        limit: pageSize,
      );
      return TreeDataTablePage(
        rows: result.rows.map(buildTreeRowNode).toList(),
        totalRowCount: result.totalCount,
      );
    },
    childrenLoader: (parent) async {
      final children = await api.fetchChildren(parent.id);
      return children.map(buildTreeRowNode).toList();
    },
  ),
)

Set id and hasChildren: true on a TreeRowNode whose rows have not been loaded. The child loader runs on its first expansion and the returned children are cached on that node. maxLevel is required when a child loader is used because the table cannot infer depth from descendants that have not been requested.

Use loadingBuilder to replace the default progress indicator and errorBuilder to present a custom root-page error. The error builder receives the error and a retry callback. A failed child request remains unloaded and is retried when the row is collapsed and expanded again.

The example app's Lazy Complex Tree Table uses this lazy source end to end and can report hundreds of thousands of rows without constructing them up front.

Expansion state #

TreeRowNode.isExpanded supplies the initial expansion state and is updated by the table after user interaction. Use TreeRowNode.onExpansionChanged when the surrounding application also needs to react to that state change.

Non-null row IDs must be unique within the loaded table. A TreeRowNode must also occur in only one place: shared node instances and cycles are rejected.

Column state #

Set a unique persistColumnStateId for each table whose interactive state should survive widget rebuilds. Width, visibility, and pinning are kept in memory for the current app session; they are not written to permanent device storage.

Examples #

The example app contains complete flat, simple tree, and multi-level tree tables.

Simple table #

[A simple flat data table]

Simple tree table #

[A data table with expandable tree rows]

Complex tree table #

[A complex data table with grouped headers and many columns]

Run the example locally:

cd example
flutter run

Issues and contributions #

Found a bug or have an idea? Search the issue tracker before opening a new issue. Pull requests are welcome in the GitHub repository.

License #

Tree Data Table is available under the MIT License.

0
likes
0
points
7
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter widget for displaying hierarchical data in a table with pagination and pinned columns.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

collection, flutter, flutter_localizations, intl

More

Packages that depend on tree_data_table