9.2 KiB
9.2 KiB
Editing — Flutter DataGrid
Overview
SfDataGrid supports inline cell editing. To enable it:
- Set
SfDataGrid.allowEditing = true - Set
SfDataGrid.navigationMode = GridNavigationMode.cell - Set
SfDataGrid.selectionModeto any value other thannone - Override
DataGridSource.buildEditWidgetto return the edit widget - Override
DataGridSource.onCellSubmitto commit the edited value
Basic Setup
SfDataGrid(
source: _employeeDataSource,
allowEditing: true,
selectionMode: SelectionMode.single,
navigationMode: GridNavigationMode.cell,
columns: [
GridColumn(columnName: 'id', label: Container(/* ... */)),
GridColumn(columnName: 'name', label: Container(/* ... */)),
GridColumn(columnName: 'designation', label: Container(/* ... */)),
GridColumn(columnName: 'salary', label: Container(/* ... */)),
],
)
DataGridSource: buildEditWidget + onCellSubmit
Provide the editing widget via buildEditWidget and commit values via onCellSubmit:
class EmployeeDataSource extends DataGridSource {
dynamic newCellValue;
TextEditingController editingController = TextEditingController();
@override
Widget? buildEditWidget(DataGridRow dataGridRow,
RowColumnIndex rowColumnIndex, GridColumn column, CellSubmit submitCell) {
final String displayText = dataGridRow
.getCells()
.firstWhereOrNull((cell) => cell.columnName == column.columnName)
?.value
?.toString() ??
'';
newCellValue = null; // reset before editing
final bool isNumericType =
column.columnName == 'id' || column.columnName == 'salary';
return Container(
padding: const EdgeInsets.all(8.0),
alignment: isNumericType ? Alignment.centerRight : Alignment.centerLeft,
child: TextField(
autofocus: true,
controller: editingController..text = displayText,
textAlign: isNumericType ? TextAlign.right : TextAlign.left,
keyboardType:
isNumericType ? TextInputType.number : TextInputType.text,
decoration: InputDecoration(
contentPadding: const EdgeInsets.fromLTRB(0, 0, 0, 16.0),
),
onChanged: (String value) {
if (value.isNotEmpty) {
newCellValue = isNumericType ? int.parse(value) : value;
} else {
newCellValue = null;
}
},
onSubmitted: (String value) {
// On mobile: manually call submitCell to trigger onCellSubmit
submitCell();
},
),
);
}
@override
Future<void> onCellSubmit(DataGridRow dataGridRow,
RowColumnIndex rowColumnIndex, GridColumn column) async {
final dynamic oldValue = dataGridRow
.getCells()
.firstWhereOrNull((cell) => cell.columnName == column.columnName)
?.value ??
'';
final int dataRowIndex = dataGridRows.indexOf(dataGridRow);
if (newCellValue == null || oldValue == newCellValue) return;
if (column.columnName == 'id') {
dataGridRows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<int>(columnName: 'id', value: newCellValue);
_employees[dataRowIndex].id = newCellValue as int;
} else if (column.columnName == 'name') {
dataGridRows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<String>(columnName: 'name', value: newCellValue);
_employees[dataRowIndex].name = newCellValue.toString();
} else if (column.columnName == 'designation') {
dataGridRows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<String>(columnName: 'designation', value: newCellValue);
_employees[dataRowIndex].designation = newCellValue.toString();
} else {
dataGridRows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<int>(columnName: 'salary', value: newCellValue);
_employees[dataRowIndex].salary = newCellValue as int;
}
// No need to call notifyListeners — DataGrid refreshes automatically
}
}
Disable Editing for a Specific Column
GridColumn(
columnName: 'id',
allowEditing: false, // overrides SfDataGrid.allowEditing
label: Container(/* ... */),
)
Edit Mode Trigger
By default, double-tap enters edit mode. Use single-tap instead:
SfDataGrid(
source: _employeeDataSource,
allowEditing: true,
selectionMode: SelectionMode.single,
navigationMode: GridNavigationMode.cell,
editingGestureType: EditingGestureType.tap,
columns: [/* ... */],
)
DataGridSource Methods for Editing
onCellBeginEdit — Control which cells can enter edit mode
Return false to prevent a cell from entering edit mode:
@override
bool onCellBeginEdit(
DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) {
if (column.columnName == 'id') {
return false; // block editing for 'id' column
}
return true;
}
Block a specific cell by row/column index:
@override
bool onCellBeginEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex,
GridColumn column) {
if (rowColumnIndex.equals(RowColumnIndex(2, 2))) {
return false;
}
return true;
}
canSubmitCell — Validate before committing
Return false to keep the cell in edit mode (focus stays on current cell):
@override
Future<bool> canSubmitCell(DataGridRow dataGridRow,
RowColumnIndex rowColumnIndex, GridColumn column) async {
if (column.columnName == 'id' && newCellValue == null) {
return false; // retain in edit mode to prevent null value
}
return true;
}
onCellSubmit — Commit edited values
Called when editing ends (via submitCell() or focus change). See Basic Setup for full example.
No need to call
notifyListenersafter updatingdataGridRows— DataGrid refreshes automatically.
onCellCancelEdit — Handle ESC key cancel
Called on Web/Desktop when Esc is pressed. canSubmitCell and onCellSubmit are not called:
@override
void onCellCancelEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex,
GridColumn column) {
// Reset any temp edit state here
}
Programmatic Editing
Begin edit at specific cell
final DataGridController _dataGridController = DataGridController();
// In widget:
SfDataGrid(
source: _employeeDataSource,
allowEditing: true,
selectionMode: SelectionMode.single,
navigationMode: GridNavigationMode.cell,
controller: _dataGridController,
columns: [/* ... */],
)
// Trigger programmatically:
_dataGridController.beginEdit(RowColumnIndex(2, 3));
End edit programmatically
_dataGridController.endEdit();
Check if current cell is in edit mode
bool isEditing = _dataGridController.isCurrentCellInEditing;
Async Editing with Loading Indicator
Use StreamController to show a loading indicator during async onCellSubmit or canSubmitCell:
StreamController<bool> loadingController = StreamController<bool>();
// In build(), wrap SfDataGrid in StreamBuilder:
StreamBuilder(
stream: loadingController.stream,
builder: (context, snapshot) {
return Stack(children: [
SfDataGrid(/* ... */),
if (snapshot.data == true)
const Center(child: CircularProgressIndicator()),
]);
},
)
// In DataGridSource:
@override
Future<void> onCellSubmit(/* ... */) async {
loadingController.add(true);
await Future<void>.delayed(const Duration(seconds: 2));
loadingController.add(false);
// ... commit logic
}
@override
Future<bool> canSubmitCell(/* ... */) async {
if (column.columnName == 'id' && newCellValue == 104) {
loadingController.add(true);
await Future<void>.delayed(const Duration(seconds: 2));
loadingController.add(false);
return false; // invalid value, stay in edit mode
}
return true;
}
Key Properties Reference
| Property / Method | Description |
|---|---|
SfDataGrid.allowEditing |
Enable cell editing globally |
SfDataGrid.navigationMode |
Must be GridNavigationMode.cell for editing |
SfDataGrid.selectionMode |
Must not be none |
SfDataGrid.editingGestureType |
tap or doubleTap (default) |
GridColumn.allowEditing |
Override editing per column |
DataGridSource.buildEditWidget |
Returns the edit widget for a cell |
DataGridSource.onCellSubmit |
Commit edited value to data source |
DataGridSource.onCellBeginEdit |
Control which cells can be edited |
DataGridSource.canSubmitCell |
Validate before committing |
DataGridSource.onCellCancelEdit |
Handle ESC key cancel |
DataGridController.beginEdit(RowColumnIndex) |
Programmatically enter edit mode |
DataGridController.endEdit() |
Programmatically end edit mode |
DataGridController.isCurrentCellInEditing |
Check if current cell is in edit mode |