grid_sheet 1.1.9
grid_sheet: ^1.1.9 copied to clipboard
A Flutter DataGrid/DataTable with minimal configuration and powerful features like filtering, formulas, pagination, editing, frozen columns, CRUD, and full customization.
Grid Sheet #
It is a highly flexible, high-performance Flutter DataTable / DataGrid package designed for building rich tabular UIs similar to Excel or spreadsheet applications, with minimal configuration and powerful built-in features. It gives developers full control over both data management and UI presentation while remaining easy to set up and extend.
It supports essential table and data-grid features such as CRUD operations, sorting, filtering, pagination, column resizing, column formula's and advanced cell customization, making it a robust alternative to Flutter’s built-in DataTable.
With Grid Sheet, you can fully customize table headers, rows, filter cells, footers, frozen columns, and context menus using your own widgets or wrapper-based designs. This flexibility makes it ideal for building Excel-like tables, and highly customized table experiences in Flutter—without sacrificing performance or simplicity.
Table of Contents #
- Features
- Installation
- Import
- Usage
- Example
- API Overview
- Configuration
- Advanced Features
- Platform Support
- Notes
- Contributing
- License
Features #
Core Functionality #
- ✅ Data Display & Management - Display tabular data with full CRUD operations on rows and columns
- ✅ Multi-Column Sorting - Sort by multiple columns with ascending/descending order
- ✅ Advanced Filtering - Filter data with multiple comparison modes (contains, exact match)
- ✅ Pagination - Built-in pagination with customizable rows per page
- ✅ Column Freezing - Pin columns to the left side for horizontal scrolling
- ✅ Column Reordering - Drag-and-drop column reordering with visual feedback
- ✅ Column Resizing - Resize columns by dragging column borders
- ✅ Cell Editing - Inline cell editing with type-specific input formatters
Selection & Navigation #
- ✅ Multi-Selection - Select multiple rows, columns, or cells simultaneously
- ✅ Keyboard Navigation - Navigate between cells using arrow keys
- ✅ Select All - Checkbox to select/deselect all visible rows
- ✅ Selection Callbacks - Events for row, column, and cell selection changes
Data Manipulation #
- ✅ Change Tracking - Track inserted, modified, and deleted rows
- ✅ Undo Operations - Revert changes to individual rows or all changes
- ✅ Bulk Operations - Clear, fill, find, and replace operations on cell ranges
- ✅ Copy/Paste - Clipboard support for tab-delimited data
- ✅ Auto-Fill - Fill cells with patterns and automatic incrementing
Visual Customization #
- ✅ Conditional Formatting - Apply background colors and text styles based on expressions
- ✅ Custom Cell Widgets - Build custom widgets for headers, filters, and data cells
- ✅ Row State Indicators - Visual indicators for inserted, modified, and deleted rows
- ✅ Theme Support - Comprehensive styling with light/dark theme support
- ✅ Custom Wrappers - Wrap header, filter, and row widgets with custom containers
Advanced Features #
- ✅ Expression Evaluation - Evaluate conditional expressions for cell editability and formatting
- ✅ Context Menus - Right-click context menus for cells, rows, and columns
- ✅ Column Visibility - Show/hide columns dynamically
- ✅ Row Height Management - Auto-fit or manually adjust row heights
- ✅ Column Width Management - Auto-fit or manually adjust column widths
- ✅ Snapshot/Restore - Save and restore grid state for undo/redo functionality
- ✅ Statistics - Calculate sum, average, min, max, median for numeric columns
- ✅ Data Export - Export to CSV with customizable options
- ✅ Loading States - Display custom loading indicators during operations
- ✅ Formula Columns - Excel-style formulas, automatic evaluation, reactive updates, and filter support
Installation #
Add grid_sheet to your pubspec.yaml:
dependencies:
grid_sheet: ^1.1.9
Then run:
flutter pub get
Import #
import 'package:grid_sheet/grid_sheet.dart';
Usage #
Basic Setup #
GridSheet(
columns: [
GridSheetColumn(
key: ValueKey('col_name'),
index: 0,
name: 'COL_NAME',
title: 'Name',
type: GridSheetColumnType.text,
width: 200,
),
GridSheetColumn(
key: ValueKey('col_age'),
index: 1,
name: 'AGE',
title: 'Age',
type: GridSheetColumnType.double,
width: 80,
textAlign: TextAlign.right,
),
GridSheetColumn(
key: ValueKey('col_active'),
index: 2,
name: 'ACTIVE',
title: 'Active',
type: GridSheetColumnType.boolean,
width: 80,
),
],
rows: List.generate(
50,
(i) => GridSheetRow(
key: ValueKey('row_$i'),
index: i,
data: ['Person $i', 20 + i, i % 2 == 0],
),
),
onLoaded: (event) {
gridManager = event.gridManager;
debugPrint('Table initialized!');
},
),
With Custom Header and Footer #
GridSheet(
columns: columns,
rows: rows,
headerWidget: (gridManager) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
ElevatedButton(
onPressed: () async {
final newRow = GridSheetRow(
key: ValueKey('row_${DateTime.now().millisecondsSinceEpoch}'),
index: gridManager.rows.length,
data: List.filled(gridManager.columns.length, null),
);
await gridManager.insertRowsAt(0, [newRow]);
},
child: Text('Add Row'),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () async {
await gridManager.deleteRowsByKeys(
gridManager.selectedRowKeys,
);
},
child: Text('Delete Selected'),
),
],
),
);
},
footerWidget: (gridManager) {
return Container(
padding: EdgeInsets.all(8),
child: Text('Total Rows: ${gridManager.rows.length}'),
);
},
)
Example #
import 'package:flutter/material.dart';
import 'package:grid_sheet/grid_sheet.dart';
void main() => runApp(GridSheetApp());
class GridSheetApp extends StatefulWidget {
const GridSheetApp({super.key});
@override
State<GridSheetApp> createState() => _GridSheetAppState();
}
class _GridSheetAppState extends State<GridSheetApp> {
late final GridSheetManager gridManager;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('GridSheet Example')),
body: GridSheet(
columns: [
GridSheetColumn(
key: ValueKey('col_name'),
index: 0,
name: 'COL_NAME',
title: 'Name',
type: GridSheetColumnType.text,
width: 200,
),
GridSheetColumn(
key: ValueKey('col_age'),
index: 1,
name: 'AGE',
title: 'Age',
type: GridSheetColumnType.double,
width: 80,
textAlign: TextAlign.right,
),
GridSheetColumn(
key: ValueKey('col_active'),
index: 2,
name: 'ACTIVE',
title: 'Active',
type: GridSheetColumnType.boolean,
width: 80,
),
],
rows: List.generate(
50,
(i) => GridSheetRow(
key: ValueKey('row_$i'),
index: i,
data: ['Person $i', 20 + i, i % 2 == 0],
),
),
onLoaded: (event) {
gridManager = event.gridManager;
debugPrint('Table initialized!');
},
),
),
);
}
}
API Overview #
GridSheet Widget #
The main widget that displays the data grid.
Key Properties:
columns- List of column definitionsrows- List of row dataconfiguration- Layout and behavior settingsstyleConfiguration- Visual styling optionsscrollConfiguration- Scrollbar customizationconditionalFormatRules- List of formatting rulesheaderWidget- Custom widget above the gridfooterWidget- Custom widget below the gridnoRowsWidget- Widget shown when no data is availableloadingWidget- Widget shown during async operationscellBuilder- Custom cell renderer (Builds a custom widget for a grid cell)contextMenuWidget- Right-click context menu builderonLoaded- Callback when grid is initializedonRowsSelected- Callback for row selection changesonColumnsSelected- Callback for column selection changesonCellsSelected- Callback for cell selection changesonCellValueChange- Callback when cell value changessortIconBuilder- Custom builder for sort direction indicators in column headersrowStateColorBuilder- Custom builder for row state background colors in the index columnrowStateIconBuilder- Custom builder for row state icons (inserted/modified/deleted indicators)headerWrapper- Optional wrapper widget for the entire header rowfilterWrapper- Optional wrapper widget for the entire filter rowrowWrapper- Optional wrapper widget for each data rowevaluator- Custom expression evaluator for conditional expressions (defaults to built inGridSheetExpressionEvaluator)
GridSheetManager #
The state manager interface providing programmatic control over the grid. Access it via the onLoaded callback or other event callbacks.
Data Operations:
// Row operations
await gridManager.insertRowsAt(index, rows);
await gridManager.deleteRowsByKeys(rowKeys);
gridManager.updateRowByKey(rowKey, newData);
// Column operations
await gridManager.insertColumnsAt(index, columns);
await gridManager.deleteColumnsByKeys(columnKeys);
await gridManager.hideColumns(['COL_NAME']);
await gridManager.showColumns(['COL_NAME']);
// Cell operations
gridManager.updateCurrentCell(rowKey: key, columnKey: key, value: value);
await gridManager.clearCellRange(startRow: 0, endRow: 5, startColumn: 0, endColumn: 2);
Selection:
// Programmatic selection
gridManager.selectRowsByKeys([rowKey1, rowKey2]);
gridManager.selectColumnsByKeys([colKey1]);
gridManager.selectCellsByKeys([cellKey1, cellKey2]);
// Query selection
final selectedRows = gridManager.selectedRowKeys;
final selectedCells = gridManager.selectedCellKeys;
Filtering & Sorting:
// Add filters
await gridManager.addFilters(
{'status': ['Active', 'Pending']},
comparison: GridSheetFilterComparison.exact,
);
// Sort by column
await gridManager.sortByColumn('age', direction: GridSheetSortDirection.desc);
// Clear all
await gridManager.clearSort();
await gridManager.removeAllFilters();
Pagination:
// Navigate
await gridManager.goToPage(3);
gridManager.nextPage();
gridManager.previousPage();
// Change page size
await gridManager.changeRowsPerPage(50);
// Query state
debugPrint('Page ${gridManager.currentPage} of ${gridManager.totalPages}');
Data Export:
// Export as CSV
final csv = gridManager.exportToCSV(
includeHeaders: true,
onlyVisibleColumns: true,
);
// Get row data as maps
final maps = gridManager.rowsDataToMapList(gridManager.rows);
// Get statistics
final stats = gridManager.getColumnStatistics('salary');
debugPrint('Average: ${stats['average']}');
Core Models #
GridSheetColumn
Note: The name property serves as the unique internal identifier for a column. Use this value consistently across expressions, conditional formatting rules, and other grid configurations to reference the column reliably.
GridSheetColumn(
key: ValueKey('unique_key'),
name: 'COL_NAME', // Internal identifier
title: 'Display Title', // Shown in header
type: GridSheetColumnType.text,
width: 150,
visible: true,
frozen: false, // Pin to left
editable: true,
sortable: true,
resize: true,
textAlign: TextAlign.left,
conditionalEditExpression: 'age > 18', // Control editability
)
GridSheetRow
GridSheetRow(
key: ValueKey('unique_key'),
index: 0,
data: ['value1', 'value2', 'value3'],
height: 40.0,
state: GridSheetRowState.existing, // existing (default), inserted, modified, deleted
)
GridSheetCell
final cellKey = GridSheetCell(
ValueKey('row_1'),
ValueKey('col_name'),
);
Configuration #
Layout Configuration #
GridSheetLayoutConfiguration(
// Pagination
enableDefaultPagination: true,
enableCustomPagination: false,
rowsPerPage: 20,
// Selection
enableRowSelectionOnFirstColumnTap: true,
enableColumnSelection: true,
enableCellSelection: true,
enableMultiSelection: true,
enableRowSelectionOnFirstColumnTap: true,
selectOnlyPageRows: false,
// Visual
enableRowHover: true,
showBorderOnSelection: false,
serialNumberColumn: true,
serialNumberColumnWidth: 60,
showRowBorders: true,
showColumnBorders: true,
showColumnFilters: true,
// Interaction
enableReorder: true,
enableRowContextMenu: true,
enableColumnContextMenu: true,
enableCellContextMenu: true,
// Sizing
headerHeight: 40,
filterHeight: 40,
rowHeight: 40,
)
Style Configuration #
GridSheetStyleConfiguration(
// Colors
gridBackgroundColor: Colors.white,
headerColor: Colors.grey[200]!,
rowColor: Colors.white,
evenRowColor: Colors.grey[50],
oddRowColor: Colors.white,
hoverColorOnRow: Colors.blue,
filterColor: Colors.grey[100]!,
selectionColor: Colors.blue[100]!,
// Borders
gridBorderColor: Colors.grey[300]!,
rowBorderColor: Colors.grey[300]!,
columnBorderColor: Colors.grey[300]!,
gridBorderRadius: BorderRadius.all(Radius.circular(4)),
// Text styles
headerTextStyle: TextStyle(fontWeight: FontWeight.bold),
cellTextStyle: TextStyle(fontSize: 14),
// Padding
leftPadding: 8,
rightPadding: 8,
)
Theming #
You can switch between light and dark themes by configuring the style options.
For a full working example, please see the Example tab.
Column Configuration #
Column Types:
GridSheetColumnType.text- String valuesGridSheetColumnType.double- Numeric values with decimal supportGridSheetColumnType.int- Integer valuesGridSheetColumnType.boolean- True/false valuesGridSheetColumnType.datetime- Date and time values
Column Features:
GridSheetColumn(
// Identity
key: ValueKey('col_1'),
name: 'internalName',
title: 'Display Name',
index: 0, // Position in data array
// Behavior
type: GridSheetColumnType.text,
editable: true,
sortable: true,
resize: true,
frozen: false,
visible: true,
noTextControllerWidget: false, // Use `noTextControllerWidget: true` for read-only columns or when using custom widgets that do not require a `TextEditingControllers`, preventing unnecessary controller creation and improving performance.
// Appearance
width: 150,
textAlign: TextAlign.left,
// Conditional cell editability
conditionalEditExpression: 'status == "Active"',
)
Advanced Features #
Sorting #
This feature works only when the column is configured with sortable = true and the user double-clicks on the column header to apply sorting. The custom icons are displayed when sorting is active.
Custom sort icons allow you to build and display your own sorting indicators in column headers. These icons are shown when sorting is applied, giving you full control over their appearance and behavior instead of using the default built-in sort icons.
GridSheetColumn(
// To enable sorting for that column
sortable: true,
);
// custom sort icons
GridSheet(
sortIconBuilder: (column, direction, priority) {
final isAsc = direction == GridSheetSortDirection.asc;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isAsc ? Icons.arrow_upward : Icons.arrow_downward,
size: 16,
color: isAsc ? Colors.green : Colors.red,
),
if (priority > 1)
Text(
' $priority',
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
),
],
);
},
)
Column Reorder #
Column reordering of column is enabled by long-pressing on a column header and dragging it to the desired position.
// To enable column reordering
GridSheetLayoutConfiguration(
enableReorder: true,
)
Column Formulas #
It supports mostly Excel-style formulas in cells, with automatic evaluation, editing, and filtering. Dependent values automatically update when source cells change.
GridSheetColumn(
name: 'FORMULA_COL',
type: GridSheetColumnType.formula, // formula type column
showFormulaInEdit: true, // shows formula when focused, else only evaluated value
);
// Can evaluate formulas on load
// Can contain any supported formula in its cells, e.g., '=F1 * 2', '=SUM(E1:F3)', '=A1 + B2'.
rows = [
['=F1 * 2'],
['=SUM(E1:F3)'],
];
Filter support: Works on both formulas and computed values
await gridManager.addFilters(
{'FORMULA_COL': ['=F1 * 2']},
comparison: GridSheetFilterComparison.exact,
);
await gridManager.addFilters(
{'FORMULA_COL': [1400]}, // e.g., if F1 value is 700
comparison: GridSheetFilterComparison.exact,
);
Conditional Formatting #
Apply visual styles to cells dynamically based on their values, enabling better data visualization and quick identification of important or out-of-range data.
GridSheet(
conditionalFormatRules: [
// Row-level formatting
GridSheetConditionalFormatRule(
name: 'HighlightHighValues',
expression: 'age > 50',
scope: GridSheetFormatScope.row,
backgroundColor: Colors.red[100],
),
// Column-level formatting
GridSheetConditionalFormatRule(
name: 'status',
expression: 'status == "Active"',
scope: GridSheetFormatScope.column,
backgroundColor: Colors.green[100],
),
// Cell-level formatting
GridSheetConditionalFormatRule(
name: 'salary',
expression: 'salary > 100000',
scope: GridSheetFormatScope.cell,
backgroundColor: Colors.orange[100],
fontStyle: TextStyle(fontWeight: FontWeight.bold),
),
],
)
Dynamic rule management: Conditional formatting rules can be added, updated or removed dynamically, allowing styles to adapt as data or requirements change.
// Add rule
await gridManager.addConditionalFormatRule(rule);
// Remove rule
await gridManager.removeConditionalFormatRule(
expression: 'age > 50',
scope: GridSheetFormatScope.row,
);
// Update rule
await gridManager.updateConditionalFormatRule(
oldExpression: 'age > 50',
scope: GridSheetFormatScope.row,
newRule: updatedRule,
);
Custom Row State Indicators #
Use custom row state indicators to display visual markers(icon/bgColor) in the serial number column when a row is modified, deleted, or inserted, helping track changes directly in the grid.
GridSheet(
rowStateColorBuilder: (row) {
if (row.isInserted) return Colors.green[50]!;
if (row.isModified) return Colors.orange[50]!;
if (row.isDeleted) return Colors.red[50]!;
return Colors.white;
},
rowStateIconBuilder: (row) {
if (row.isInserted) {
return Icon(Icons.add_circle, color: Colors.green, size: 16);
}
if (row.isModified) {
return Icon(Icons.edit, color: Colors.orange, size: 16);
}
if (row.isDeleted) {
return Icon(Icons.delete, color: Colors.red, size: 16);
}
return null;
},
)
Custom Wrappers #
Wrappers allow you to decorate grid sections such as headers, filters, and rows by wrapping the widgets with custom UI (e.g., padding, cards, borders, or shadows) without changing the cell content itself.
Important: When using wrappers with padding, you must use StyleConfiguration.leftPadding and StyleConfiguration.rightPadding for horizontal spacing to avoid overflow issues.
GridSheetStyleConfiguration styleConfiguration = GridSheetStyleConfiguration(
leftPadding: 16, // Use these for horizontal padding
rightPadding: 16,
);
GridSheet(
styleConfiguration: styleConfiguration,
headerWrapper: (context, child, isFrozen) {
return Padding(
padding: isFrozen
? EdgeInsets.only(left: styleConfiguration.leftPadding)
: EdgeInsets.only(right: styleConfiguration.rightPadding),
child: Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.only(
top: 4,
bottom: 4,
),
shape: RoundedRectangleBorder(
borderRadius: isFrozen
? BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
)
: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: child,
),
);
},
filterWrapper: (context, child, isFrozen) {
return Padding(
padding: isFrozen
? EdgeInsets.only(left: styleConfiguration.leftPadding)
: EdgeInsets.only(right: styleConfiguration.rightPadding),
child: Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.only(
bottom: 4,
),
shape: RoundedRectangleBorder(
borderRadius: isFrozen
? BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
)
: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: child,
),
);
},
rowWrapper: (context, child, isFrozen, isHovered) {
return Padding(
padding: isFrozen
? EdgeInsets.only(left: styleConfiguration.leftPadding)
: EdgeInsets.only(right: styleConfiguration.rightPadding),
child: Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.only(bottom: 4),
shape: RoundedRectangleBorder(
borderRadius: isFrozen
? BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
)
: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: child,
),
);
},
)
Custom Expression Evaluator #
Default Built-in GridSheetExpressionEvaluator
Extend ExpressionEvaluator to implement your own custom logic for conditional expressions:
Note: Handling null values and values type in the expression evaluator is crucial for a stable expression engine.
class CustomEvaluator extends ExpressionEvaluator {
@override
dynamic eval(Expression expression, Map<String, dynamic> context) {
// Custom evaluation logic
// Handle null values, custom functions, special operators, etc.
return super.eval(expression, context);
}
}
GridSheet(
evaluator: CustomEvaluator(),
columns: [
GridSheetColumn(
name: 'status',
conditionalEditExpression: 'age >= 18 && status == "Active"',
// This expression will be evaluated using CustomEvaluator
),
],
)
Custom Cell Widgets #
Build custom widgets for any cell type:
IGridSheetCustomCellWidgetBuilder
The IGridSheetCustomCellWidgetBuilder abstract class allows developers to create and render custom Flutter widgets for individual cells in the GridSheet. By implementing this interface, you can fully control the appearance and behavior of cells at the row, column, or cell level.
This enables embedding interactive and complex widgets—such as buttons, icons, switches, progress indicators, or formatted layouts—directly within grid cells instead of using default text-based rendering. The renderer receives contextual information including the current row, cell value, row index, and column index, allowing conditional UI rendering and user interaction handling.
Using IGridSheetCustomCellWidgetBuilder provides greater flexibility and customization while maintaining GridSheet performance and structure, making it suitable for advanced tabular UI requirements.
late IGridSheetCustomCellWidgetBuilder _cellRenderer;
@override
void initState() {
super.initState();
_cellRenderer = CellWidgetBuilder(
onValueChanged: (rowIndex, colIndex, value) {
rows[rowIndex][colIndex] = value;
},
);
}
GridSheet(
// Your own custom widgets for cells.
cellBuilder: (cell) {
switch (cell.kind) {
case GridSheetCellKind.selectAll:
// select all rows cell
return _cellRenderer.buildSelectAll(context, cell);
case GridSheetCellKind.indexing:
// row-indexed cells
return _cellRenderer.buildIndexes(context, cell);
case GridSheetCellKind.header:
// column-header cells
return _cellRenderer.buildHeader(context, cell);
case GridSheetCellKind.filter:
// column-filter cells
return _cellRenderer.buildFilter(context, cell.column, cell);
case GridSheetCellKind.row:
// column-row cells
final row = cell.row!;
final column = cell.column;
return _cellRenderer.buildCell(context, column, row, cell);
}
},
)
// Sample custom cell builder class for reference
class CellWidgetBuilder implements IGridSheetCustomCellWidgetBuilder {
final CellValueChanged? onValueChanged;
CellWidgetBuilder({this.onValueChanged});
GridSheetCellWidgetType _resolveRenderType(GridSheetColumn column) {
if (GridSheetColumnType.boolean == column.type) {
return GridSheetCellWidgetType.checkbox;
}
if (GridSheetColumnType.datetime == column.type) {
return GridSheetCellWidgetType.date;
}
return GridSheetCellWidgetType.text;
}
@override
Widget buildCell(
BuildContext context,
GridSheetColumn column,
GridSheetRow row,
GridSheetCellContext cell,
) {
if (column.name == 'Status') {
return _buildStatusChip(row.data[column.index].toString());
}
return getCellWidget(cell, context, row, column);
}
Widget getCellWidget(
GridSheetCellContext cell,
BuildContext context,
GridSheetRow row,
GridSheetColumn column,
) {
switch (_resolveRenderType(column)) {
case GridSheetCellWidgetType.checkbox:
return _buildCheckbox(cell, row, column);
case GridSheetCellWidgetType.date:
return _buildDateCell(context, cell, row, column);
case GridSheetCellWidgetType.text:
default:
return _buildTextCell(context, cell, row, column);
}
}
}
Using Default and Custom Cell Widgets
You can use built-in default cell widgets for certain cell kinds and provide custom widgets only where needed. To do this, return a widget for the cell kinds you want to customize, and return null for the remaining kinds so the grid falls back to its default rendering.
GridSheet(
//Example: If you want to use the default built-in widgets for filter cells, simply return `null` for the `filter` cell kind. The grid will automatically fall back to its default renderer.
cellBuilder: (cell) {
switch (cell.kind) {
case GridSheetCellKind.selectAll:
return _cellRenderer.buildSelectAll(context, cell);
case GridSheetCellKind.indexing:
return _cellRenderer.buildIndexes(context, cell);
case GridSheetCellKind.header:
return _cellRenderer.buildHeader(context, cell);
case GridSheetCellKind.filter:
return null; // fall back to its default renderer.
case GridSheetCellKind.row:
final row = cell.row!;
final column = cell.column;
return _cellRenderer.buildCell(context, column, row, cell);
}
},
)
Context Menus #
Add right-click context menus:
GridSheet(
contextMenuWidget: (info) {
if (info.target == GridSheetContextMenuTarget.cell) {
final cellInfo = info as GridSheetCellContextInfo;
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.copy),
title: Text('Copy'),
onTap: () {
// Copy cell value
},
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Clear'),
onTap: () async {
await cellInfo.gridManager.clearCellRange(
startRow: cellInfo.row!.index,
endRow: cellInfo.row!.index,
startColumn: cellInfo.column.index,
endColumn: cellInfo.column.index,
);
},
),
],
),
);
}
return null;
},
)
Pagination #
GridSheet provides built-in pagination support with both default UI and custom implementation options.
Default Pagination
GridSheet(
columns: _getColumns(),
rows: _getRows(),
configuration: GridSheetLayoutConfiguration(
enableDefaultPagination: true,
),
styleConfiguration: _styleConfiguration,
),
Custom Pagination
GridSheet(
columns: _getColumns(),
rows: _getRows(),
configuration: GridSheetLayoutConfiguration(
// both should not be true
enableCustomPagination: true,
enableDefaultPagination: false,
),
styleConfiguration: _styleConfiguration,
footerWidget: (manager) {
/* you can utilize built-in widget or your own custom widget */
return GridSheetPagination(
gridManager: manager,
styleConfiguration: PaginationConfiguration(
backgroundColor: _styleConfiguration.rowColor,
borderColor: _styleConfiguration.rowBorderColor,
selectionColor: _styleConfiguration.selectionColor,
textStyle: theme.textTheme.bodySmall!,
),
);
},
),
Selection Events #
GridSheet provides comprehensive selection callbacks for rows, columns, and cells. Each callback provides detailed information about the selection state and selected data.
Row Selection Event
Triggered when row selection changes (click on row index or first column). To enable the Select All Rows feature from the serial number column header, ensure the following property is set: enableRowSelectionOnFirstColumnTap: true
GridSheet(
configuration: GridSheetLayoutConfiguration(
enableRowSelectionOnFirstColumnTap: true,
enableMultiSelection: true, // Allow selecting multiple rows
),
onRowsSelected: (event) {
// Access selection data
debugPrint('Selected ${event.selectedRowKeys.length} rows');
// Get selected row objects
for (final row in event.selectedRows) {
debugPrint('Row ${row.index}: ${row.data}');
debugPrint('Row state: ${row.state}'); // existing, inserted, modified, deleted
}
// Get selected rows as maps (column name → value)
for (final rowMap in event.selectedRowsData) {
debugPrint('Name: ${rowMap['name']}, Age: ${rowMap['age']}');
}
// Access grid manager for further operations
final manager = event.gridManager;
// Example: Delete selected rows
// await manager.deleteRowsByKeys(event.selectedRowKeys);
},
)
Column Selection Event
Triggered when column selection changes (click on column header).
GridSheet(
configuration: GridSheetLayoutConfiguration(
enableColumnSelection: true,
enableMultiSelection: true,
),
onColumnsSelected: (event) {
// Access selection data
debugPrint('Selected ${event.selectedColumnKeys.length} columns');
// Get selected column objects
for (final column in event.selectedColumns) {
debugPrint('Column: ${column.name} (${column.title})');
debugPrint('Type: ${column.type}, Width: ${column.width}');
}
// Get column names
debugPrint('Selected columns: ${event.selectedColumnNames.join(", ")}');
// Get all values from selected columns
event.selectedColumnsData.forEach((columnName, values) {
debugPrint('$columnName has ${values.length} values');
debugPrint('Values: $values');
});
// Access grid manager
final manager = event.gridManager;
// Example: Hide selected columns
// await manager.hideColumns(event.selectedColumnNames);
// Example: Auto-fit selected columns
// await manager.autoFitColumnWidths(event.selectedColumnNames);
},
)
Cell Selection Event
Triggered when cell selection changes (click on individual cells).
GridSheet(
configuration: GridSheetLayoutConfiguration(
enableCellSelection: true,
enableMultiSelection: true,
),
onCellsSelected: (event) {
// Access selection data
debugPrint('Selected ${event.selectedCells.length} cells');
// Get selected cell keys
for (final cellKey in event.selectedCells) {
debugPrint('Cell: Row=${cellKey.rowKey}, Column=${cellKey.columnKey}');
}
// Get selected cell data grouped by column
event.selectedCellsData.forEach((columnName, values) {
debugPrint('Column "$columnName": $values');
// Example: ['Active', 'Pending', 'Active']
});
// Access grid manager
final manager = event.gridManager;
// Example: Clear selected cells
// manager.clearCellsByKeys(event.selectedCells);
// Example: Fill selected cells with a value
// manager.fillCellsByKeys(event.selectedCells, 'New Value');
},
)
No Rows Custom Widget #
Provide a custom widget to display when the grid has no rows. This allows you to show personalized messages or visuals instead of leaving the grid empty. If no custom widget is supplied, the grid automatically falls back to its default built-in widget.
GridSheet(
columns: _getColumns(),
rows: _getRows(),
configuration: GridSheetLayoutConfiguration(
enableDefaultPagination: true,
),
styleConfiguration: styleConfiguration,
conditionalFormatRules: getFormattingRules(),
onLoaded: (event) {
gridManager = event.gridManager;
},
noRowsWidget: Padding(
padding: EdgeInsets.all(4),
child: Text(
'No data to display',
style: theme.textTheme.bodySmall,
),
),
);
Custom Loading Widget #
Provide your own custom loading widget to display while the grid is fetching or processing data, replacing the default loading indicator with a personalized UI for async operations.
Note: If loadingWidget is not provided, no loading indicator will be shown, as there is no default loading widget in the grid.
GridSheet(
columns: _getColumns(),
rows: _getRows(),
configuration: GridSheetLayoutConfiguration(
enableDefaultPagination: true,
),
styleConfiguration: styleConfiguration,
conditionalFormatRules: getFormattingRules(),
onLoaded: (event) {
gridManager = event.gridManager;
},
loadingWidget: loadingWidget,
);
Widget get loadingWidget {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text(
'Processing...',
style: TextStyle(color: Colors.white, fontSize: 16),
),
],
),
);
}
Data Operations #
Change Tracking:
// Get changed data
final inserted = gridManager.insertedRows;
final modified = gridManager.modifiedRows;
final deleted = gridManager.deletedRows;
final allChanges = gridManager.changedRows;
// Persist changes
for (final row in inserted) {
await api.createRow(gridManager.rowsDataToMapList([row]).first);
}
// Undo changes
await gridManager.undoRowChanges(rowKey);
await gridManager.undoAllRowsChanges();
Bulk Operations:
// Find cells
final highSalaries = gridManager.findCells(
predicate: (value, row, column) => value is num && value > 100000,
columnNames: ['salary'],
);
// Find and replace
final count = await gridManager.findAndReplace(
findValue: 'Active',
replaceValue: 'Completed',
exactMatch: true,
);
// Fill pattern
await gridManager.fillCells(
startRowKey: ValueKey('row_1'),
endRowKey: ValueKey('row_10'),
columnKey: ValueKey('col_sequence'),
value: 1,
increment: true, // Auto-increment numbers
);
Auto-Sizing:
// Auto-fit column widths
await gridManager.autoFitColumnWidths(['firstName', 'lastName']);
await gridManager.autoFitAllColumnWidths();
// Auto-fit row heights
await gridManager.autoFitRowHeights([rowKey1, rowKey2]);
await gridManager.autoFitAllRowHeights();
Platform Support #
GridSheet supports all Flutter platforms:
- ✅ Android
- ✅ iOS
- ✅ Web
- ✅ Windows
- ✅ macOS
- ✅ Linux
Notes #
Performance Considerations #
- Use pagination for large datasets (recommended: 50-100 rows per page)
- Limit the number of conditional format rules for better performance
- Avoid excessive column reordering in production
- The controller pool automatically manages TextEditingControllers to prevent memory leaks
Limitations #
- Browser storage APIs (localStorage, sessionStorage) are not supported in artifacts mode
- Column expressions use the
expressionspackage for evaluation - Custom evaluators can be provided for advanced expression handling
- Multi-column sorting preserves order by priority (first added = highest priority)
Best Practices #
- Use stable keys - Always provide unique
ValueKey<String>for rows and columns - Leverage callbacks - Use
onCellValueChangeto track changes in real-time - Manage state externally - Use
GridSheetManagerfrom callbacks to update external state - Optimize rendering - Use
noTextControllerWidget: truefor read-only columns or when using custom widgets that do not require aTextEditingController, preventing unnecessary controller creation and improving performance. - Handle loading states - Provide a
loadingWidgetfor better UX during async operations
Contributions #
If Grid Sheet has been helpful, please consider giving our Pub package a 👍. You can also support the project by spreading the word and sharing it with others.
We truly appreciate your support and contributions to the ongoing development of this project. We welcome discussions, feedback, and pull requests—feel free to share your ideas or suggestions on our GitHub repository. Issues Tracker
License #
This project is licensed under the MIT License - see the LICENSE file for details.