layout | title | description | platform | control | documentation |
---|---|---|---|---|---|
post |
Editing in Flutter DataGrid | Flutter DataTable | Syncfusion |
Learn here all about how to perform editing in Syncfusion Flutter DataGrid (SfDataGrid) widget and more. |
flutter |
SfDataGrid |
ug |
The SfDataGrid supports editing the cell values by setting the SfDataGrid.allowEditing property as true and SfDataGrid.navigationMode as cell, and SfDataGrid.selectionMode as other than none.
By default, the SfDataGrid does not load any widget when the cell is moved into edit mode. Users must load any widget when the cell is moved into edit mode by returning the required widget through the DataGridSource.buildEditWidget method in DataGridSource class.
The following arguments are passed in the buildEditWidget
method.
- row: Gets the DataGridRow of the SfDataGrid.
- rowColumnIndex: Gets the current row and column index of the DataGrid.
- column: Gets the Grid Column of the SfDataGrid.
- submitCell: Programmatically call to end the editing. Typically, this method can be called when the widget completes its editing. For example,
TextField.onSubmitted
method is called whenever TextField ends its editing. So, you can simply call submitCell method. This will automatically call the DataGridSource.
You are recommended to save the edited value through editors in the DataGridSource.onCellSubmit method. The onCellSubmit
method will be called whenever the submitCell method from the buildEditWidget method is called, or other cells are navigated when a cell is in edit mode.
The following example shows how to enable editing in Datagrid and commit the edited cell value in the onCellSubmit
method.
{% tabs %} {% highlight dart %}
@override Widget build(BuildContext context) { return Scaffold( body: SfDataGrid( source: _employeeDataSource, allowEditing: true, selectionMode: SelectionMode.single, navigationMode: GridNavigationMode.cell, columns: [ GridColumn( columnName: 'id', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'ID', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Name', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'designation', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'Salary', overflow: TextOverflow.ellipsis, ))), ])); }
class EmployeeDataSource extends DataGridSource { /// Helps to hold the new value of all editable widgets. /// Based on the new value we will commit the new value into the corresponding /// DataGridCell on the onCellSubmit method. dynamic newCellValue;
/// Helps to control the editable text in the [TextField] widget. TextEditingController editingController = TextEditingController();
@override Future onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { final dynamic oldValue = dataGridRow .getCells() .firstWhereOrNull((DataGridCell dataGridCell) => dataGridCell.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;
}
}
@override Widget? buildEditWidget(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column, CellSubmit submitCell) { // Text going to display on editable widget final String displayText = dataGridRow .getCells() .firstWhereOrNull((DataGridCell dataGridCell) => dataGridCell.columnName == column.columnName) ?.value ?.toString() ?? '';
// The new cell value must be reset.
// To avoid committing the [DataGridCell] value that was previously edited
// into the current non-modified [DataGridCell].
newCellValue = null;
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,
decoration: InputDecoration(
contentPadding: const EdgeInsets.fromLTRB(0, 0, 0, 16.0),
),
keyboardType: isNumericType ? TextInputType.number : TextInputType.text,
onChanged: (String value) {
if (value.isNotEmpty) {
if (isNumericType) {
newCellValue = int.parse(value);
} else {
newCellValue = value;
}
} else {
newCellValue = null;
}
},
onSubmitted: (String value) {
// In Mobile Platform.
// Call [CellSubmit] callback to fire the canSubmitCell and
// onCellSubmit to commit the new value in single place.
submitCell();
},
),
);
} }
{% endhighlight %} {% endtabs %}
NOTE
Download demo application from GitHub.
To disable the editing for a particular column, set the GridColumn.allowEditing property as false
.
{% tabs %} {% highlight dart %}
@override Widget build(BuildContext context) { return Scaffold( body: SfDataGrid( source: _employeeDataSource, allowEditing: true, selectionMode: SelectionMode.single, navigationMode: GridNavigationMode.cell, columns: [ GridColumn( columnName: 'id', allowEditing: false, label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'ID', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Name', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'designation', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'Salary', overflow: TextOverflow.ellipsis, ))) ])); }
{% endhighlight %} {% endtabs %}
By default, the cell will be moved to edit mode when you double-tap the cells. Enable the editing to be performed by tapping, and set the SfDataGrid.editingGestureType property as tap.
{% tabs %} {% highlight dart %}
@override Widget build(BuildContext context) { return Scaffold( body: SfDataGrid( source: _employeeDataSource, allowEditing: true, selectionMode: SelectionMode.single, navigationMode: GridNavigationMode.cell, editingGestureType: EditingGestureType.tap, columns: [ GridColumn( columnName: 'id', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'ID', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Name', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'designation', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'Salary', overflow: TextOverflow.ellipsis, ))) ])); }
{% endhighlight %} {% endtabs %}
The DataGridSource.onCellBeginEdit method is called when the cell enters edit mode. Return false if you don’t want to move any specific cells to edit mode. The following arguments are passed in this method:
- row: Gets the DataGridRow of the SfDataGrid.
- rowColumnIndex: Gets the current row and column index of the DataGrid.
- column: Gets the Grid Column of the SfDataGrid.
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override bool onCellBeginEdit( DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { if (column.columnName == 'id') { // Return false, to restrict entering into the editing. return false; } else { return true; } } }
{% endhighlight %} {% endtabs %}
The DataGridSource.canSubmitCell is called before the cell is ending its editing. If you want to restrict the cell from being end its editing, you can return false. onCellSubmit will be called only if the canSubmitCell
returns true. The following arguments are passed in this method,
- row: Gets the DataGridRow of the SfDataGrid.
- rowColumnIndex: Gets the current row and column index of the DataGrid.
- column: Gets the Grid Column of the SfDataGrid.
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override Future canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { if (column.columnName == 'id' && newCellValue == null) { // Return false, to retain in edit mode. // To avoid null value for cell return false; } else { return true; } } }
{% endhighlight %} {% endtabs %}
The DataGridSource.onCellSubmit method is called when the editing is completed. You are recommended to save the edited values to the underlying collection in this method. It makes sense to handle the entire editing operation in this single method.
NOTE
There is no need to call thenotifyListeners
after you update the DataGridRows. DataGrid will refresh the UI automatically.
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override Future onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { final dynamic oldValue = dataGridRow .getCells() .firstWhereOrNull((DataGridCell dataGridCell) => dataGridCell.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;
}
} }
{% endhighlight %} {% endtabs %}
The DataGridSource.onCellCancelEdit method is called when you press the Esc
key from Web and Desktop platforms. The canSubmitCell and onCellSubmit will not be called when the Esc
key is pressed.
NOTE
No need to call the notifyListener inside it.
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override void onCellCancelEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { // handle the cancel editing code here } }
{% endhighlight %} {% endtabs %}
The SfDataGrid allows moving the cell into edit mode programmatically by calling the DataGridController.beginEdit method.
{% tabs %} {% highlight dart %}
final DataGridController _dataGridController = DataGridController();
@override Widget build(BuildContext context) { return Scaffold( body: Column(children: [ TextButton( child: Text("Begin Edit"), onPressed: () { _dataGridController.beginEdit(RowColumnIndex(2, 3)); }), Expanded( child: SfDataGrid( source: _employeeDataSource, allowEditing: true, selectionMode: SelectionMode.single, navigationMode: GridNavigationMode.cell, controller: _dataGridController, columns: [ GridColumn( columnName: 'id', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'ID', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Name', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'designation', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'Salary', overflow: TextOverflow.ellipsis, ))) ])) ])); }
{% endhighlight %} {% endtabs %}
The SfDataGrid.endEdit method can be called to programmatically end the editing for a specific cell.
{% tabs %} {% highlight dart %}
final DataGridController _dataGridController = DataGridController();
@override Widget build(BuildContext context) { return Scaffold( body: Column(children: [ TextButton( child: Text("End Edit"), onPressed: () { _dataGridController.endEdit(); }), Expanded( child: SfDataGrid( source: _employeeDataSource, allowEditing: true, selectionMode: SelectionMode.single, navigationMode: GridNavigationMode.cell, controller: _dataGridController, columns: [ GridColumn( columnName: 'id', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'ID', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Name', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'designation', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerLeft, child: Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.centerRight, child: Text( 'Salary', overflow: TextOverflow.ellipsis, ))) ])) ])); }
{% endhighlight %} {% endtabs %}
You can check whether the current cell is in editing mode or not by using the DataGridController.isCurrentCellInEditing property.
{% tabs %} {% highlight dart %}
final DataGridController _dataGridController = DataGridController();
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('SfDataGrid Editing'), ), body: Column( children: [ TextButton( onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( content: Text( 'Cell is in Edit Mode: ${_dataGridController.isCurrentCellInEditing}'), )); }, child: const Text('In Edit Mode')), Expanded( child: SfDataGrid( source: _employeeDataSource, allowEditing: true, controller: _dataGridController, selectionMode: SelectionMode.single, navigationMode: GridNavigationMode.cell, columnWidthMode: ColumnWidthMode.fitByCellValue, editingGestureType: EditingGestureType.tap, columns: [ GridColumn( columnName: 'id', label: Container( padding: const EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.center, child: const Text( 'ID', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'name', label: Container( padding: const EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.center, child: const Text( 'Name', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'designation', label: Container( padding: const EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.center, child: const Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: const EdgeInsets.symmetric(horizontal: 16.0), alignment: Alignment.center, child: const Text( 'Salary', overflow: TextOverflow.ellipsis, ))) ], ), ), ], ), ); }
{% endhighlight %} {% endtabs %}
To cancel the editing for a specific cell or column at run time, override the onCellBeginEdit method in the DataGridSource
class and return false for a specific cell or column.
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override bool onCellBeginEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) {
// Editing prevented for the cell at RowColumnIndex(2,2).
if (rowColumnIndex.equals(RowColumnIndex(2, 2))) {
return false;
} else {
return true;
}
} }
{% endhighlight %} {% endtabs %}
The following code example shows how to cancel the specific column from being entered into edit mode,
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override bool onCellBeginEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { if (column.columnName == 'id' || column.columnName == 'salary') { return false; } else { return true; } } }
{% endhighlight %} {% endtabs %}
You can override the canSubmitCell from DataGridSource class and return false based on your criteria.
If you return a false from the canSubmitCell
method, the Datagrid doesn't allow the focus to the other widget, which is added outside the DataGrid. The focus would always be on the currentCell
.
{% tabs %} {% highlight dart %}
class EmployeeDataSource extends DataGridSource { @override Future canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) { if (newCellValue == null || newCellValue == '') { // Editing widget will retain in view. // onCellSubmit method will not fire. return false; } else { // Allow to call the onCellSubmit. return true; } } }
{% endhighlight %} {% endtabs %}
The editing can be asynchronously performed by handling the DataGridSource.canSubmitCell method, which is called before the cell ends its editing and DataGridSource.onCellSubmit, which is called when the cell ends its editing.
The following example shows how to show the loading indicator for a specific amount of time when the cell ends its editing and also shows the loading indicator until the cell checks whether entered value is valid.
{% tabs %} {% highlight Dart %}
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
StreamController loadingController = StreamController(); List employees = [];
class _MyHomePageState extends State { late EmployeeDataSource employeeDataSource;
@override void initState() { super.initState(); employees = getEmployeeData(); employeeDataSource = EmployeeDataSource(employeeData: employees); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Syncfusion Flutter DataGrid'), ), body: StreamBuilder( stream: loadingController.stream, builder: (BuildContext context, AsyncSnapshot snapshot) { return Stack(children: [ SfDataGrid( source: employeeDataSource, columnWidthMode: ColumnWidthMode.fill, allowEditing: true, gridLinesVisibility: GridLinesVisibility.both, headerGridLinesVisibility: GridLinesVisibility.both, navigationMode: GridNavigationMode.cell, editingGestureType: EditingGestureType.doubleTap, selectionMode: SelectionMode.single, columns: [ GridColumn( columnName: 'id', label: Container( padding: const EdgeInsets.all(16.0), alignment: Alignment.centerRight, child: const Text( 'ID', ))), GridColumn( columnName: 'name', label: Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.centerLeft, child: const Text('Name'))), GridColumn( columnName: 'designation', label: Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.centerLeft, child: const Text( 'Designation', overflow: TextOverflow.ellipsis, ))), GridColumn( columnName: 'salary', label: Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.centerRight, child: const Text('Salary'))), ], ), if (snapshot.data == true) const Center( child: CircularProgressIndicator(), ), ]); })); } }
@override Future onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) async { loadingController.add(true); await Future.delayed(const Duration(seconds: 2)); loadingController.add(false); final dynamic oldValue = dataGridRow .getCells() .firstWhereOrNull((DataGridCell dataGridCell) => dataGridCell.columnName == column.columnName) ?.value ?? '';
final int dataRowIndex = _employeeData.indexOf(dataGridRow);
if (newCellValue == null || oldValue == newCellValue) {
return;
}
if (column.columnName == 'id') {
_employeeData[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<int>(columnName: 'id', value: newCellValue);
employees[dataRowIndex].id = newCellValue as int;
} else if (column.columnName == 'name') {
_employeeData[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<String>(columnName: 'name', value: newCellValue);
employees[dataRowIndex].name = newCellValue.toString();
} else if (column.columnName == 'designation') {
_employeeData[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<String>(columnName: 'designation', value: newCellValue);
employees[dataRowIndex].designation = newCellValue.toString();
} else {
_employeeData[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<int>(columnName: 'salary', value: newCellValue);
employees[dataRowIndex].salary = newCellValue as int;
}
} @override Future canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) async { if (column.columnName == 'id' && newCellValue == 104) { loadingController.add(true); await Future.delayed(const Duration(seconds: 2)); loadingController.add(false); return false; } else { return true; } }
{% endhighlight %} {% endtabs %}