Pull to Refresh in Flutter DataGrid (SfDataGrid)

8 Jul 202624 minutes to read

The SfDataGrid provides support to add more data at runtime by using the pull-to-refresh feature. You can enable the pull-to-refresh option by setting the SfDataGrid.allowPullToRefresh property to true and overriding the DataGridSource.handleRefresh method to add new data to the data source at runtime, then notify the data grid about the changes.

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';

class Employee {
  Employee(this.id, this.name, this.designation, this.salary);

  final int id;
  final String name;
  final String designation;
  final int salary;
}

class PullToRefreshDemoState extends State<PullToRefreshDemo> {
  List<Employee> _employees = <Employee>[];
  late EmployeeDataSource _employeeDataSource;

  @override
  void initState() {
    super.initState();
    _employees = getEmployeeData();
    _employeeDataSource = EmployeeDataSource(employeeData: _employees);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SfDataGrid(
        allowPullToRefresh: true,
        source: _employeeDataSource,
        columns: <GridColumn>[
          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),
            ),
          ),
        ],
      ),
    );
  }
}

List<Employee> getEmployeeData() {
  return [
    Employee(10001, 'James', 'Project Lead', 20000),
    Employee(10002, 'Kathryn', 'Manager', 30000),
    Employee(10003, 'Lara', 'Developer', 15000),
    Employee(10004, 'Michael', 'Designer', 15000),
    Employee(10005, 'Martin', 'Developer', 15000),
    Employee(10006, 'Newberry', 'Developer', 15000),
    Employee(10007, 'Balnc', 'Developer', 15000),
    Employee(10008, 'Perry', 'Developer', 15000),
    Employee(10009, 'Gable', 'Developer', 15000),
    Employee(10010, 'Grimes', 'Developer', 15000),
  ];
}

class EmployeeDataSource extends DataGridSource {
  late List<Employee> _employees = [];

  EmployeeDataSource({required List<Employee> employeeData}) {
    _employees = employeeData;
    buildDataGridRows();
  }

  List<DataGridRow> dataGridRows = [];

  @override
  List<DataGridRow> get rows => dataGridRows;

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    return DataGridRowAdapter(
      cells: row.getCells().map<Widget>((dataGridCell) {
        return Container(
          alignment:
              (dataGridCell.columnName == 'id' ||
                  dataGridCell.columnName == 'salary')
              ? Alignment.centerRight
              : Alignment.centerLeft,
          padding: EdgeInsets.symmetric(horizontal: 16.0),
          child: Text(
            dataGridCell.value.toString(),
            overflow: TextOverflow.ellipsis,
          ),
        );
      }).toList(),
    );
  }

  @override
  Future<void> handleRefresh() async {
    // Simulate network delay for fetching new data from server
    await Future.delayed(Duration(seconds: 2));
    _addMoreRows(_employees, 15);
    buildDataGridRows();
    notifyListeners();
  }

  void buildDataGridRows() {
    dataGridRows = _employees
        .map<DataGridRow>(
          (dataGridRow) => DataGridRow(
            cells: [
              DataGridCell<int>(columnName: 'id', value: dataGridRow.id),
              DataGridCell<String>(columnName: 'name', value: dataGridRow.name),
              DataGridCell<String>(
                columnName: 'designation',
                value: dataGridRow.designation,
              ),
              DataGridCell<int>(
                columnName: 'salary',
                value: dataGridRow.salary,
              ),
            ],
          ),
        )
        .toList();
  }

  void _addMoreRows(List<Employee> employees, int count) {
    final Random random = Random();
    final startIndex = employees.isNotEmpty ? employees.length : 0;
    final endIndex = startIndex + count;
    for (int i = startIndex; i < endIndex; i++) {
      employees.add(
        Employee(
          1000 + i,
          _names[random.nextInt(_names.length)],
          _designation[random.nextInt(_designation.length)],
          10000 + random.nextInt(10000),
        ),
      );
    }
  }

  final List<String> _names = <String>[
    'Welli',
    'Blonp',
    'Folko',
    'Furip',
    'Folig',
    'Picco',
    'Frans',
    'Warth',
    'Linod',
    'Simop',
    'Merep',
    'Riscu',
    'Seves',
    'Vaffe',
    'Alfki',
  ];

  final List<String> _designation = <String>[
    'Project Lead',
    'Developer',
    'Manager',
    'Designer',
    'System Analyst',
    'CEO',
  ];
}

Reference: Download the demo application from GitHub.

flutter datagrid shows default view of refresh indicator

Customizing the refresh indicator

SfDataGrid displays Flutter’s RefreshIndicator during pull-to-refresh actions. You can customize the refresh indicator by using the SfDataGrid.refreshIndicatorStrokeWidth and SfDataGrid.refreshIndicatorDisplacement properties.

To set the indicator’s color and background color, use the ColorScheme properties within ThemeData. The primary color in ColorScheme controls the indicator’s color.

Note: The deprecated ThemeData.accentColor property has been replaced with ColorScheme.primary in Flutter 3.0+. Use ColorScheme for modern Flutter applications.

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';

class CustomRefreshIndicatorDemoState
    extends State<CustomRefreshIndicatorDemo> {
  List<Employee> _employees = <Employee>[];
  late EmployeeDataSource _employeeDataSource;

  @override
  void initState() {
    super.initState();
    _employees = getEmployeeData();
    _employeeDataSource = EmployeeDataSource(employeeData: _employees);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Theme(
        data: ThemeData(
          brightness: Brightness.light,
          canvasColor: Colors.lightBlue,
          colorScheme: const ColorScheme.light(primary: Colors.white),
        ),
        child: SfDataGrid(
          allowPullToRefresh: true,
          source: _employeeDataSource,
          refreshIndicatorStrokeWidth: 3.0,
          refreshIndicatorDisplacement: 60.0,
          columns: <GridColumn>[
            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),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Reference: Download the demo application from GitHub.

flutter datagrid shows customized refresh indicator

Programmatic Pull to Refresh

You can trigger data refresh programmatically using the SfDataGridState.refresh() method. By default, the refresh indicator is displayed during the refresh operation. To refresh data without showing the refresh indicator, pass false to the showRefreshIndicator parameter.

The refresh() method calls the DataGridSource.handleRefresh() method, which is a Future<void> that returns when the refresh operation completes. If an exception occurs during handleRefresh(), the data grid will handle it gracefully and the refresh state will be reset.

Note: Use a GlobalKey<SfDataGridState> to access the refresh method from outside the SfDataGrid widget.

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';

EmployeeDataSource _employeeDataSource = EmployeeDataSource();
List<Employee> _employees = <Employee>[];
final GlobalKey<SfDataGridState> key = GlobalKey<SfDataGridState>();

class PullToRefreshSampleState extends State<PullToRefreshSample> {
  @override
  void initState() {
    super.initState();
    _employees = getEmployeeData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SfDataGrid(
        key: key,
        allowPullToRefresh: true,
        source: _employeeDataSource,
        columns: <GridColumn>[
          GridColumn(
            columnName: 'id',
            label: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16.0),
              alignment: Alignment.centerRight,
              child: const Text('ID', overflow: TextOverflow.ellipsis),
            ),
          ),
          GridColumn(
            columnName: 'name',
            label: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16.0),
              alignment: Alignment.centerLeft,
              child: const Text('Name', overflow: TextOverflow.ellipsis),
            ),
          ),
          GridColumn(
            columnName: 'designation',
            label: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16.0),
              alignment: Alignment.centerLeft,
              child: const Text('Designation', overflow: TextOverflow.ellipsis),
            ),
          ),
          GridColumn(
            columnName: 'salary',
            label: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16.0),
              alignment: Alignment.centerRight,
              child: const Text('Salary', overflow: TextOverflow.ellipsis),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          key.currentState!.refresh();
        },
      ),
    );
  }
}

class EmployeeDataSource extends DataGridSource {
  EmployeeDataSource() {
    buildDataGridRows();
  }

  List<DataGridRow> dataGridRows = [];

  @override
  List<DataGridRow> get rows => dataGridRows;

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    return DataGridRowAdapter(
      cells: row.getCells().map<Widget>((dataGridCell) {
        return Container(
          alignment:
              (dataGridCell.columnName == 'id' ||
                  dataGridCell.columnName == 'salary')
              ? Alignment.centerRight
              : Alignment.centerLeft,
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Text(
            dataGridCell.value.toString(),
            overflow: TextOverflow.ellipsis,
          ),
        );
      }).toList(),
    );
  }

  @override
  Future<void> handleRefresh() async {
    await Future.delayed(const Duration(seconds: 5));
    _addMoreRows(_employees, 15);
    buildDataGridRows();
    notifyListeners();
  }

  void buildDataGridRows() {
    dataGridRows = _employees
        .map<DataGridRow>(
          (dataGridRow) => DataGridRow(
            cells: [
              DataGridCell<int>(columnName: 'id', value: dataGridRow.id),
              DataGridCell<String>(columnName: 'name', value: dataGridRow.name),
              DataGridCell<String>(
                columnName: 'designation',
                value: dataGridRow.designation,
              ),
              DataGridCell<int>(
                columnName: 'salary',
                value: dataGridRow.salary,
              ),
            ],
          ),
        )
        .toList();
  }

  void _addMoreRows(List<Employee> employees, int count) {
    final Random random = Random();
    final startIndex = employees.isNotEmpty ? employees.length : 0,
        endIndex = startIndex + count;
    for (int i = startIndex; i < endIndex; i++) {
      employees.add(
        Employee(
          1000 + i,
          _names[random.nextInt(_names.length - 1)],
          _designation[random.nextInt(_designation.length - 1)],
          10000 + random.nextInt(10000),
        ),
      );
    }
  }

  final List<String> _names = <String>[
    'Welli',
    'Blonp',
    'Folko',
    'Furip',
    'Folig',
    'Picco',
    'Frans',
    'Warth',
    'Linod',
    'Simop',
    'Merep',
    'Riscu',
    'Seves',
    'Vaffe',
    'Alfki',
  ];

  final List<String> _designation = <String>[
    'Project Lead',
    'Developer',
    'Manager',
    'Designer',
    'System Analyst',
    'CEO',
  ];
}

Reference: Download the demo application from GitHub.

flutter datagrid shows programmatic refresh indicator