I enabled editing, pagination, and filtering (via UI) in SFDataGrid. I didn't override handlePageChange in order for sorting and filtering to be done for the whole set of rows. I have 5 rows per page.
My data consists of 8 records: 5 records on page 1 and 3 records on page 2.
There are 2 issues.
E/flutter (22765): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: RangeError (index): Invalid value: Valid value range is empty: 0
E/flutter (22765): #0 List.[] (dart:core-patch/growable_array.dart:264:36)
E/flutter (22765): #1 SfDataGridState._processCellUpdate
package:syncfusion_flutter_datagrid/…/datagrid_widget/sfdatagrid.dart:1903
E/flutter (22765): #2 SfDataGridState._handleDataGridPropertyChangeListeners
package:syncfusion_flutter_datagrid/…/datagrid_widget/sfdatagrid.dart:2230
E/flutter (22765): #3 DataGridSourceChangeNotifier._notifyDataGridPropertyChangeListeners
package:syncfusion_flutter_datagrid/…/datagrid_widget/sfdatagrid.dart:4191
E/flutter (22765): #4 notifyDataGridPropertyChangeListeners
package:syncfusion_flutter_datagrid/…/datagrid_widget/sfdatagrid.dart:4205
E/flutter (22765): #5 CurrentCellManager.onCellSubmit
package:syncfusion_flutter_datagrid/…/selection/selection_manager.dart:1939
E/flutter (22765): #6 CurrentCellManager.onCellBeginEdit.submitCell
package:syncfusion_flutter_datagrid/…/selection/selection_manager.dart:1792
E/flutter (22765): #7 StudentDataSource.buildEditWidget.<anonymous closure>
package:drivingschool/datasource/student_data_source.dart:331
E/flutter (22765): <asynchronous suspension>
E/flutter (22765):
I/IMM_LC (22765): hsifw() - flag : 0
I/IMM_LC (22765): hsifw() - mService.hideSoftInput
D/InsetsController(22765): controlAnimationUnchecked: Added types=8 animType=1 host=team.cdl.drivingschool/team.cdl.drivingschool.MainActivity from=android.view.InsetsController.applyAnimation:1576 android.view.InsetsController.applyAnimation:1557 android.view.InsetsController.hide:1093
Here is some code snapshots for the issues above
---------------------------------------------------------
class StudentDataSource extends DataGridSource {
StudentDataSource() {
buildDataGridRows();
}
List<Student> _students = [];
List<DataGridRow> dataGridRows = [];
//Map<String, Student> _studentsMap = {};
dynamic newCellValue;
TextEditingController editingController = TextEditingController();
DataPagerController pageController = DataPagerController();
// pagination logic
final double _dataPagerHeight = 60.0;
int _rowsPerPage = 5;
void buildDataGridRows() {
dataGridRows = _students.map<DataGridRow>((student) => student.getDataGridRow()).toList();
}
double getPageCount() {
double pageCount = _students.isEmpty ? 1 : (_students.length / _rowsPerPage).ceil() * 1.0;
return pageCount;
}
void updateDataGridSource() {
notifyListeners();
}
@override
void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) async {
final int pageAdjustment = pageController.selectedPageIndex * _rowsPerPage; // _rowsPerPage = 5;
final int dataRowIndex = isAnyFilterEnabled() ? dataGridRows.indexOf(dataGridRow) : pageAdjustment + rowColumnIndex.rowIndex;
if (dataRowIndex < dataGridRows.length) {
DataGridRow curGridRow = dataGridRows[dataRowIndex];
dynamic oldValue;
for (var cell in curGridRow.getCells()) {
if (column.columnName == cell.columnName) {
oldValue = cell.value ?? '';
}
}
if (newCellValue == null || oldValue == newCellValue) return;
if (column.columnName == Student.colId) {
dataGridRows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] =
DataGridCell<String>(columnName: column.columnName, value: newCellValue);
_students[dataRowIndex].id = newCellValue.toString();
} else if .......
... save data in backend
}
}
@override
bool canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) {
return true; // Return false, to retain in edit mode; or super.canSubmitCell(dataGridRow, rowColumnIndex, column);
}
@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() ??
'';
newCellValue = null;
...
return Container(
padding: const EdgeInsets.all(5.0),
alignment: isNumericType ? Alignment.centerRight : Alignment.centerLeft,
child: TextField(
autofocus: true,
controller: editingController..text = displayText,
textAlign: isNumericType ? TextAlign.right : TextAlign.left,
decoration: const InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 0, 0, 5.0),
//errorText: errText,
),
inputFormatters: customFormatters,
keyboardType: keyboardType,
onChanged: (String value) {
if (value.isNotEmpty) {
if (isNumericType) {
newCellValue = int.parse(value);
} else {
newCellValue = value;
}
} else {
newCellValue = null;
}
},
onSubmitted: (String value) async {
bool isValidated = await validateField(context!, column.columnName, value);
if (isValidated) {
//try {
submitCell();
//} catch (e) {
//devtools.log("Caught Exception ${e.toString()}");
//}
} else {
editingController.text = displayText;
}
},
),
);
}
// VIEW
class _StudentsViewState extends State<StudentsView> {
late StudentDataSource _studentDataSource;
late DataGridController _dataGridController;
......
void initState() {
_studentDataSource = StudentDataSource();
_dataGridController = DataGridController();
super.initState();
}
@override
Widget build(BuildContext context) {
......
return Column(
children: [
SizedBox(
height: constraint.maxHeight - _studentDataSource.dataPagerHeight,
width: constraint.maxWidth,
child: SfDataGridTheme(
data: SfDataGridThemeData(
headerColor: const Color.fromARGB(255, 250, 235, 208),
headerHoverColor: const Color.fromARGB(255, 226, 250, 248), // for web
),
child: SfDataGrid(
source: _studentDataSource,
allowFiltering: true,
allowSorting: true,
allowTriStateSorting: true,
selectionMode: SelectionMode.single,
defaultColumnWidth: 137,
headerGridLinesVisibility: GridLinesVisibility.both,
gridLinesVisibility: GridLinesVisibility.both,
allowEditing: true,
navigationMode: GridNavigationMode.cell,
editingGestureType: EditingGestureType.doubleTap,
controller: _dataGridController,
columns: getStudentColumns(),
allowSwiping: true,
rowsPerPage: _studentDataSource.rowsPerPage,
onFilterChanged: (DataGridFilterChangeDetails details) =>
_studentDataSource.filterChanged(details)),
),
),
// ignore: sized_box_for_whitespace
Container(
height: _studentDataSource.dataPagerHeight,
child: SfDataPager(
delegate: _studentDataSource,
pageCount: _studentDataSource.getPageCount(),
controller: _studentDataSource.pageController,
direction: Axis.horizontal,
))
],
);
......
Hi Igor,
Regarding: When I go to page 2, I edit the 3rd record on page 2. When editing is done, 3rd record on page 1 is updated instead, which is very dangerous.
Can please let us know, whether you are generating the DataGridRow only for the current page by overriding handlePageChange in DataGridSource or creating DataGrid with the whole underlying collection?
Regarding: When I manually filter data and try to edit any cell after that, exceptions will occur and the row will be gone from the DataGrid completely.
We are checking this at our end. We will check and let you know about this issue.
Regards,
Tamilarasan
Hi Tamilarasan,
Here is my answer:
Can please let us know, whether you are generating the DataGridRow only for the current page by overriding handlePageChange in DataGridSource or creating DataGrid with the whole underlying collection?
I do not override handlePageChange in DataGridSource as mentioned in the very first thread. I generate DataGrid for the whole underlying collection. For that, I use a StreamBuilder to retrieve data from the Firebase cloud store. So in this case, the whole collection (currently 8 records only) is already available before DataGrid is rendered. Here is the code for review below
Thanks,
Igor
Hi Igor,
We have validated the reported issues, and we are able to replicate both issue at our end. We have considered this as a bug and logged a bug report in our feedback portal. We will fix the reported issue and include the changes in our upcoming weekly patch release, which is expected to be rolled out on January 10, 2022. We will let you know once it is published. We appreciate your patience and understanding until then.
Feedback Link: https://www.syncfusion.com/feedback/40215/editing-of-filtered-data-when-paging-is-applied-does-not-work-properly
Regards,
Tamilarasan
Thanks for the feedback.
Regards,
Igor
Igor, the fix for the reported issue “Editing of filtered data in pagination does not work properly” has been included in the latest DataGrid version 20.4.43. Update your DataGrid to get the issue resolved.
Thank you. Looks like that behavior is fixed but I haven't deeply tested it yet.
I found another bug, or please let me know if there is a solution to the following:
On the grid, my filtering is enabled. When I add some filter to the column by hand, SFDataPager.pageCount property is updated via _studentDataSource.getPageCount() and changes are reflected on the screen - see code below. This is good and the correct number of pages is shown on the SfDataPager. However, once I clear the filter from the same column by hand, SFDataPager.pageCount property IS NOT updated, i.e. _studentDataSource.getPageCount() is not called causing the screen to show incorrect page numbers.
Workaround looks ugly so far. I think you have to ensure that internally sfDataPager._pageCount is updated when the filter is cleared on any column. It also will be a great idea to have a method allowing updating pageCount from datasource and those changes reflected on the screen.
Please let me know if I missed anything or is there a way to make that work properly
Kind Regards,
Igor
Igor,
Based on the information you've provided, it appears that the solution to this issue involves updating the page count in the SfDataGrid.onFilterChanged callback. We have previously shared a sample that demonstrates this approach and have also provided a code snippet as an example. We recommend that you review the shared sample and code snippet and compare them to your implementation to ensure that they are identical.
void _updatePageCount() { final rowsCount = _employeeDataSource.filterConditions.isNotEmpty ? _employeeDataSource.effectiveRows.length : employees.length; pageCount = (rowsCount / _rowsPerPage).ceilToDouble(); }
@override void initState() { super.initState(); _employeeDataSource = EmployeeDataSource(); _updatePageCount(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('PageNavigation Demo')), body: LayoutBuilder(builder: (context, constraints) { return Column(children: [ SizedBox( height: constraints.maxHeight - 60, child: SfDataGrid( allowFiltering: true, onFilterChanged: (details) { setState(() { _updatePageCount(); }); }, source: _employeeDataSource, columnWidthMode: ColumnWidthMode.fill, columns: getColumns)), SizedBox( height: 60, child: SfDataPager( pageCount: pageCount, delegate: _employeeDataSource), ), ]); })); } |
Hi Tamil,
I made something similar in my code using onFilterChanged but I thought it was a workaround due to the need to call setState(). setState() will trigger a refresh causing a call to the server to retrieve data (via StreamBuilder) which I want to avoid. In order not to make the server call, you will need to introduce a set of variables that will keep track of whether it was a filter change or not and make a server call or not. And this whole thing needs to be done simply to reflect a correct page number for the filtered set of data.
Thanks for the heads up though. I will keep it this way for now. If you introduce other ways to handle page number refresh, please let me know.
Regards,
Igor