Flutter Basic Table
A comprehensive and high-performance table widget for Flutter with Map-based column management, advanced sorting, selection, theming, and interactive features.
๐ What's New in v2.0.0
โก Revolutionary Map-Based Architecture
- 80% faster column reordering - No more row data reorganization
- ID-based column management - Direct access, no index confusion
- Persistent sort states - Sorting survives column reordering
- Enhanced type safety - Compile-time column validation
๐ฏ Key Improvements
- Performance: Dramatically improved column operations
- Maintainability: Cleaner, more intuitive API
- Developer Experience: Advanced debugging and visualization
- Flexibility: Runtime column management made easy
โ ๏ธ Breaking Changes: v2.0.0 introduces significant API changes. See our Migration Guide for upgrading from v1.x.
โจ Features
๐๏ธ Core Architecture
- Map-based column system with ID-based identification
- Order-driven rendering with automatic sorting
- Type-safe cell mapping with automatic fallbacks
- Intelligent state management with data validation
๐ Table Functionality
- Advanced sorting with persistent state across reordering
- Drag-and-drop column reordering with order management
- Row selection with individual and bulk operations
- Cell interactions (tap, double-tap, right-click)
- Variable row heights with custom height support
๐จ Visual & Theming
- Comprehensive theming system for all components
- Custom tooltips with overflow detection and formatters
- Status indicators with generic enum support
- Responsive design with flexible column widths
- Synchronized scrollbars with smooth interactions
๐ ๏ธ Developer Tools
- Debug visualization with column order display
- Data validation with integrity checking
- Performance monitoring with built-in benchmarks
- Statistics generation for data analysis
๐ฆ Installation
Add this to your package's pubspec.yaml
file:
dependencies:
flutter_basic_table: ^2.0.1
Then run:
flutter pub get
๐ Quick Start
Basic Usage
import 'package:flutter/material.dart';
import 'package:flutter_basic_table/flutter_basic_table.dart';
class MyTableWidget extends StatefulWidget {
const MyTableWidget({super.key});
@override
State<MyTableWidget> createState() => _MyTableWidgetState();
}
class _MyTableWidgetState extends State<MyTableWidget> {
// Define columns using a Map for better management and reordering.
Map<String, BasicTableColumn> _columns = {
'id': BasicTableColumn(id: 'id', name: 'ID', order: 0, minWidth: 50),
'name': BasicTableColumn(id: 'name', name: 'Name', order: 1, minWidth: 150),
'age': BasicTableColumn(id: 'age', name: 'Age', order: 2, minWidth: 80),
'status': BasicTableColumn(
id: 'status',
name: 'Status',
order: 3,
minWidth: 120,
// Custom tooltip for status column
tooltipFormatter: (value) => 'Current status: $value',
),
'action': BasicTableColumn(id: 'action', name: 'Action', order: 4, minWidth: 100),
};
// Sample data for rows.
List<BasicTableRow> _rows = [];
// Keep track of selected rows.
Set<int> _selectedRows = {};
// Manage sorting state.
final ColumnSortManager _sortManager = ColumnSortManager();
@override
void initState() {
super.initState();
_generateSampleData();
}
void _generateSampleData() {
_rows = List.generate(20, (rowIndex) {
final status = MyStatus.values[rowIndex % MyStatus.values.length];
final statusConfig = {
MyStatus.active: StatusConfig.badge(color: Colors.green, text: 'Active'),
MyStatus.inactive: StatusConfig.badge(color: Colors.red, text: 'Inactive'),
MyStatus.pending: StatusConfig.badge(color: Colors.orange, text: 'Pending'),
}[status]!;
return BasicTableRow(
index: rowIndex,
cells: {
'id': BasicTableCell.text((rowIndex + 1).toString()),
'name': BasicTableCell.text('User ${rowIndex + 1}'),
'age': BasicTableCell.text((20 + rowIndex).toString()),
'status': BasicTableCell.status(status, statusConfig),
'action': BasicTableCell.widget(
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Action for row $rowIndex')),
);
},
child: const Text('Do Something'),
),
),
},
height: rowIndex % 2 == 0 ? 50.0 : 60.0, // Example of custom row height
);
});
_applySorting(); // Apply initial sorting if any
}
void _applySorting() {
if (_sortManager.hasSortedColumn) {
final sortedColumnId = _sortManager.currentSortedColumnId!;
final sortState = _sortManager.getSortState(sortedColumnId);
_rows.sort((a, b) {
final aValue = a.getComparableValue(sortedColumnId);
final bValue = b.getComparableValue(sortedColumnId);
int compareResult = aValue.compareTo(bValue);
if (sortState == ColumnSortState.descending) {
compareResult = -compareResult;
}
return compareResult;
});
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Basic Table Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: BasicTable(
columns: _columns,
rows: _rows,
theme: BasicTableThemeData(
headerTheme: BasicTableHeaderCellTheme(
enableSorting: true, // Enable sorting on headers
enableReorder: true, // Enable column reordering
),
checkboxTheme: BasicTableCheckboxCellTheme(
enabled: true, // Enable row selection checkboxes
),
dataRowTheme: BasicTableDataRowTheme(
backgroundColor: Colors.blue.withOpacity(0.02),
textStyle: const TextStyle(color: Colors.deepPurple),
),
),
selectedRows: _selectedRows,
onRowSelectionChanged: (index, selected) {
setState(() {
if (selected) {
_selectedRows.add(index);
} else {
_selectedRows.remove(index);
}
});
},
onSelectAllChanged: (selectAll) {
setState(() {
if (selectAll) {
_selectedRows = Set<int>.from(
_rows.map((row) => row.index));
} else {
_selectedRows.clear();
}
});
},
onRowTap: (index) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Row $index tapped!')),
);
},
onRowDoubleTap: (index) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Row $index double tapped!')),
);
},
onColumnReorder: (columnId, newOrder) {
setState(() {
_columns = BasicTableColumn.reorderColumn(_columns, columnId, newOrder);
});
},
onColumnSortById: (columnId, sortState) {
_sortManager.setSortState(columnId, sortState);
_applySorting();
},
sortManager: _sortManager, // Pass the sort manager
),
),
);
}
}
enum MyStatus { active, inactive, pending }
Advanced Features
Custom Cell Types
// Status indicator cells
BasicTableCell.status(
MyStatus.active, // Use your custom enum
StatusConfig.simple(color: Colors.green, text: 'Active'),
)
// Custom widget cells
BasicTableCell.widget(
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
// Your action here
},
),
)
// Rich text cells with custom styling
BasicTableCell.text(
'Important',
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.red),
backgroundColor: Colors.yellow.withOpacity(0.2),
)
Column Management
// Create columns with order management
final columns = {
'id': BasicTableColumn.simple(name: 'ID', order: 0),
'name': BasicTableColumn.simple(name: 'Name', order: 1),
'status': BasicTableColumn(
id: 'status',
name: 'Status',
order: 2,
forceTooltip: true,
tooltipFormatter: (value) => 'Current status: $value',
),
};
// Reorder columns programmatically
final reorderedColumns = BasicTableColumn.reorderColumn(
columns,
'status',
0, // Move to first position
);
// Normalize orders if needed
final normalizedColumns = BasicTableColumn.normalizeOrders(columns);
State Management with ColumnSortManager
import 'package:flutter/foundation.dart'; // For ChangeNotifier
import 'package:flutter_basic_table/flutter_basic_table.dart';
class TableController extends ChangeNotifier {
final ColumnSortManager sortManager = ColumnSortManager();
Map<String, BasicTableColumn> columns = {}; // Initialize with your columns
List<BasicTableRow> rows = []; // Initialize with your rows
void sortColumn(String columnId, ColumnSortState state) {
sortManager.setSortState(columnId, state);
if (state != ColumnSortState.none) {
rows.sort((a, b) {
final valueA = a.getComparableValue(columnId);
final valueB = b.getComparableValue(columnId);
final comparison = valueA.compareTo(valueB);
return state == ColumnSortState.ascending ? comparison : -comparison;
});
}
notifyListeners();
}
void reorderColumn(String columnId, int newOrder) {
columns = BasicTableColumn.reorderColumn(columns, columnId, newOrder);
notifyListeners();
}
}
๐จ Theming
Create beautiful, consistent table designs:
import 'package:flutter/material.dart';
import 'package:flutter_basic_table/flutter_basic_table.dart';
final myTheme = BasicTableThemeData(
headerTheme: const BasicTableHeaderCellTheme(
backgroundColor: Color(0xFFE0F2F7), // Light blue
textStyle: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF01579B)), // Dark blue
enableReorder: true,
enableSorting: true,
),
dataRowTheme: const BasicTableDataRowTheme(
backgroundColor: Colors.white,
border: BorderSide(color: Color(0xFFE0E0E0), width: 0.5), // Light grey
),
selectionTheme: BasicTableSelectionTheme(
selectedRowColor: Colors.blue.withOpacity(0.1),
hoverRowColor: Colors.grey.withOpacity(0.05),
),
scrollbarTheme: BasicTableScrollbarTheme(
showHorizontal: true,
showVertical: true,
hoverOnly: true,
color: Colors.blue.withOpacity(0.7),
trackColor: Colors.blue.withOpacity(0.1),
),
borderTheme: const BasicTableBorderTheme(
tableBorder: BorderSide(color: Color(0xFFBDBDBD), width: 1.0), // Medium grey
),
tooltipTheme: const BasicTableTooltipTheme(
backgroundColor: Color(0xFF303030), // Dark grey
textColor: Colors.white,
fontSize: 13.0,
),
);
โ ๏ธ Deprecations in v2.0.1
To streamline the API and promote best practices, the following have been deprecated:
BasicTable.fromColumnList
factory constructor:- Reason: Encourages the use of the more flexible and robust
Map<String, BasicTableColumn>
for column definitions. - Migration: Use the default
BasicTable
constructor and define your columns as aMap<String, BasicTableColumn>
.
- Reason: Encourages the use of the more flexible and robust
BasicTable.fromStringData
factory constructor:- Reason: Promotes direct usage of
BasicTableRow
objects, which offer more control and type safety. - Migration: Convert your raw string data into
List<BasicTableRow>
usingBasicTableRow.fromStrings
before passing it to the defaultBasicTable
constructor.
- Reason: Promotes direct usage of
BasicTableConfig
class:- Reason: All configuration options previously managed by
BasicTableConfig
have been integrated directly intoBasicTableThemeData
and its sub-themes. This provides a more unified and flexible theming approach. - Migration: Use
BasicTableThemeData
and its nested theme classes (e.g.,BasicTableHeaderCellTheme
,BasicTableCheckboxCellTheme
) to configure your table's appearance and behavior.
- Reason: All configuration options previously managed by
These deprecated elements will be removed in a future major version. Please update your code accordingly.
๐ฑ Platform Support
- โ Android
- โ iOS
- โ Web
- โ Windows
- โ macOS
- โ Linux
๐ง Migration from v1.x
If you're upgrading from v1.x, please follow our comprehensive Migration Guide which includes:
- Step-by-step conversion instructions
- Before/after code examples
- Common gotchas and solutions
- Performance optimization tips
Quick Migration Example
// Before (v1.x)
final columns = [
BasicTableColumn(name: 'ID', minWidth: 60),
BasicTableColumn(name: 'Name', minWidth: 120),
];
final rows = [
BasicTableRow(
cells: [
BasicTableCell.text('1'),
BasicTableCell.text('John'),
],
index: 0,
),
];
// After (v2.0)
final columns = {
'id': BasicTableColumn(id: 'id', name: 'ID', order: 0, minWidth: 60),
'name': BasicTableColumn(id: 'name', name: 'Name', order: 1, minWidth: 120),
};
final rows = [
BasicTableRow(
cells: {
'id': BasicTableCell.text('1'),
'name': BasicTableCell.text('John'),
},
index: 0,
),
];
๐ Performance
Benchmarks (v2.0 vs v1.x)
Operation | v1.x | v2.0 | Improvement |
---|---|---|---|
Column Reordering | O(n) | O(1) | 80% faster |
Cell Lookup | O(1) | O(1) | 20% faster |
Sort State Persistence | โ | โ | 100% reliable |
Memory Usage | Baseline | +5% | Acceptable overhead |
Performance Tips
// Cache sorted columns for multiple renders
final sortedColumns = BasicTableColumn.mapToSortedList(columns);
// Use batch operations for multiple changes
final updatedRow = row.updateCells({
'name': BasicTableCell.text('New Name'),
'status': BasicTableCell.text('Updated'),
});
// Validate data integrity periodically
// (Note: validateTableData is a placeholder, implement your own validation logic)
// if (!validateTableData(columns, rows)['is_valid']) {
// // Handle validation errors
// }
๐งช Examples
Check out the comprehensive example application that demonstrates:
- Basic table setup with Map-based columns
- Advanced sorting with persistent states
- Column reordering with visual feedback
- Row selection with bulk operations
- Custom theming with multiple presets
- Debug features for development
- Performance monitoring tools
Run the example:
cd example
flutter run
๐ค Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/kihyun1998/flutter_basic_table.git
# Install dependencies
flutter pub get
# Run tests
flutter test
# Run example
cd example && flutter run
๐ API Documentation
Comprehensive API documentation is available at pub.dev/documentation/flutter_basic_table.
Key Classes
- BasicTable: Main table widget with Map-based architecture
- BasicTableColumn: Column definition with ID and order management
- BasicTableRow: Row data with Map-based cell structure
- ColumnSortManager: Advanced sort state management
- BasicTableThemeData: Comprehensive theming system
- StatusConfig: Generic status indicator configuration
๐ Issues and Feedback
Please file issues, bugs, and feature requests on our GitHub Issues page.
When reporting issues, please include:
- Flutter version
- Package version
- Platform (iOS, Android, Web, etc.)
- Minimal reproduction code
- Expected vs actual behavior
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- Thanks to all contributors who helped make this library better
- Inspired by modern data table implementations across platforms
- Built with โค๏ธ for the Flutter community
Made with โค๏ธ by KiHyun Park
โญ Star this repo if you found it helpful! โญ