Paging in Flutter DataGrid (SfDataGrid)

16 Jun 202124 minutes to read

The datagrid interactively supports the manipulation of data using SfDataPager control. This provides support to load data in segments when dealing with large volumes of data. SfDataPager can be placed above or below based on the requirement to easily manage data paging.

The datagrid performs paging of data using the SfDataPager. To enable paging, follow below procedure

NOTE

The SfDataPager.visibleItemsCount property default value is 5.

The following code example illustrates using SfDataPager with the datagrid control:

final int rowsPerPage = 15;

final double dataPagerHeight = 60.0;

List<OrderInfo> orders = [];

List<OrderInfo> paginatedOrders = [];

final OrderInfoDataSource _orderInfoDataSource = OrderInfoDataSource();

@override
Widget build(BuildContext context) {
  return LayoutBuilder(builder: (context, constraint) {
    return Column(
      children: [
        SizedBox(
          height: constraint.maxHeight - dataPagerHeight,
          width: constraint.maxWidth,
          child: SfDataGrid(
            source: _orderInfoDataSource,
            columnWidthMode: ColumnWidthMode.fill,
            columns: <GridColumn>[
              GridTextColumn(
                columnName: 'orderID',
                label: Container(
                  padding: EdgeInsets.symmetric(horizontal: 16.0),
                  alignment: Alignment.centerRight,
                  child: Text(
                    'Order ID',
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ),
              GridTextColumn(
                  columnName: 'customerID',
                  label: Container(
                    padding: EdgeInsets.symmetric(horizontal: 16.0),
                    alignment: Alignment.centerLeft,
                    child: Text(
                      'Customer Name',
                      overflow: TextOverflow.ellipsis,
                    ),
                  )),
              GridTextColumn(
                columnName: 'orderDate',
                label: Container(
                  padding: EdgeInsets.symmetric(horizontal: 16.0),
                  alignment: Alignment.centerRight,
                  child: Text(
                    'Order Date',
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ),
              GridTextColumn(
                columnName: 'freight',
                label: Container(
                  padding: EdgeInsets.symmetric(horizontal: 16.0),
                  alignment: Alignment.center,
                  child: Text(
                    'Freight',
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ),
            ],
          ),
        ),
        Container(
          height: dataPagerHeight,
          child: SfDataPager(
            delegate: _orderInfoDataSource,
            pageCount: orders.length / rowsPerPage,
            direction: Axis.horizontal,
          ),
        )
      ],
    );
  });
}

class OrderInfoDataSource extends DataGridSource{
  OrderInfoDataSource() {
    paginatedOrders = orders.getRange(0, 19).toList(growable: false);
    buildPaginateDataGridRows();
  }

  List<DataGridRow> dataGridRows = [];

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

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    return DataGridRowAdapter(
        cells: row.getCells().map<Widget>((dataGridCell) {
      if (dataGridCell.columnName == 'orderID') {
        return Container(
          padding: EdgeInsets.symmetric(horizontal: 16.0),
          alignment: Alignment.centerRight,
          child: Text(
            dataGridCell.value.toString(),
            overflow: TextOverflow.ellipsis,
          ),
        );
      } else if (dataGridCell.columnName == 'customerID') {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerLeft,
            child: Text(
              dataGridCell.value.toString(),
              overflow: TextOverflow.ellipsis,
            ));
      } else if (dataGridCell.columnName == 'orderDate') {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              DateFormat.yMd().format(dataGridCell.value).toString(),
              overflow: TextOverflow.ellipsis,
            ));
      } else {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.center,
            child: Text(
              NumberFormat.currency(locale: 'en_US', symbol: '\$')
                  .format(dataGridCell.value)
                  .toString(),
              overflow: TextOverflow.ellipsis,
            ));
      }
    }).toList());
  }
  
  @override
  Future<bool> handlePageChange(int oldPageIndex, int newPageIndex) async {
    int startIndex = newPageIndex * rowsPerPage;
    int endIndex = startIndex + rowsPerPage;
    if(startIndex < orders.length && endIndex <=  orders.length){
      paginatedOrders = orders.getRange(startIndex, endIndex).toList(growable: false);
      buildPaginatedDataGridRows();
      notifyListeners();
    }else{
      paginatedOrders = [];
    }

    return true;
  }

  void buildPaginatedDataGridRows() {
    dataGridRows = paginatedOrders.map<DataGridRow>((dataGridRow) {
      return DataGridRow(cells: [
        DataGridCell(columnName: 'orderID', value: dataGridRow.orderID),
        DataGridCell(columnName: 'customerID', value: dataGridRow.customerID),
        DataGridCell(columnName: 'orderDate', value: dataGridRow.orderData),
        DataGridCell(columnName: 'freight', value: dataGridRow.freight),
      ]);
    }).toList(growable: false);
  }
}

flutter datapager with datagrid

Callbacks

The SfDataPager provides onPageNavigationStart and onPageNavigationEnd callbacks to listen the page navigation in widget level.

Typically, these callbacks are used to show and hide loading indicator.

final OrderInfoDataSource _orderInfoDataSource = OrderInfoDataSource();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: LayoutBuilder(
      builder: (context, constraints) {
        return Row(children: [
          Column(
            children: [
              SizedBox(
                  height: constraints.maxHeight - 60,
                  width: constraints.maxWidth,
                  child: buildDataGrid(constraints)),
              Container(
                height: 60,
                width: constraints.maxWidth,
                child: SfDataPager(
                  pageCount: orders.length / rowsPerPage,
                  direction: Axis.horizontal,
                  onPageNavigationStart: (int pageIndex) {
                    //You can do your customization
                  },
                  delegate: _orderInfoDataSource,
                  onPageNavigationEnd: (int pageIndex) {
                    //You can do your customization
                  },
                ),
              )
            ],
          ),
        ]);
      },
    ),
  );
}

Widget buildDataGrid(BoxConstraints constraint) {
  return SfDataGrid(
      source: _orderInfoDataSource,
      columnWidthMode: ColumnWidthMode.fill,
      columns: <GridColumn>[
        GridTextColumn(
          columnName: 'orderID',
          label: Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              'Order ID',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
        GridTextColumn(
            columnName: 'customerID',
            label: Container(
              padding: EdgeInsets.symmetric(horizontal: 16.0),
              alignment: Alignment.centerLeft,
              child: Text(
                'Customer Name',
                overflow: TextOverflow.ellipsis,
              ),
            )),
        GridTextColumn(
          columnName: 'orderDate',
          label: Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              'Order Date',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
        GridTextColumn(
          columnName: 'freight',
          label: Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.center,
            child: Text(
              'Freight',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
      ],
    );
}

class OrderInfoDataSource extends DataGridSource{
  OrderInfoDataSource() {
    paginatedOrders = orders.getRange(0, 19).toList(growable: false);
    buildPaginatedDataGridRows();
  }

  List<DataGridRow> dataGridRows = [];

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

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    return DataGridRowAdapter(
        cells: row.getCells().map<Widget>((dataGridCell) {
      if (dataGridCell.columnName == 'orderID') {
        return Container(
          padding: EdgeInsets.symmetric(horizontal: 16.0),
          alignment: Alignment.centerRight,
          child: Text(
            dataGridCell.value.toString(),
            overflow: TextOverflow.ellipsis,
          ),
        );
      } else if (dataGridCell.columnName == 'customerID') {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerLeft,
            child: Text(
              dataGridCell.value.toString(),
              overflow: TextOverflow.ellipsis,
            ));
      } else if (dataGridCell.columnName == 'orderDate') {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              DateFormat.yMd().format(dataGridCell.value).toString(),
              overflow: TextOverflow.ellipsis,
            ));
      } else {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.center,
            child: Text(
              NumberFormat.currency(locale: 'en_US', symbol: '\$')
                  .format(dataGridCell.value)
                  .toString(),
              overflow: TextOverflow.ellipsis,
            ));
      }
    }).toList());
  }
  
  @override
  Future<bool> handlePageChange(int oldPageIndex, int newPageIndex) async {
    int startIndex = newPageIndex * rowsPerPage;
    int endIndex = startIndex + rowsPerPage;
    if(startIndex < orders.length && endIndex <=  orders.length){
      paginatedOrders = orders.getRange(startIndex, endIndex).toList(growable: false);
      buildPaginatedDataGridRows();
      notifyListeners();
    }else{
      paginatedOrders = [];
    }

    return true;
  }

  void buildPaginatedDataGridRows() {
    dataGridRows = paginatedOrders.map<DataGridRow>((dataGridRow) {
      return DataGridRow(cells: [
        DataGridCell(columnName: 'orderID', value: dataGridRow.orderID),
        DataGridCell(columnName: 'customerID', value: dataGridRow.customerID),
        DataGridCell(columnName: 'orderDate', value: dataGridRow.orderData),
        DataGridCell(columnName: 'freight', value: dataGridRow.freight),
      ]);
    }).toList(growable: false);
  }
}

Asynchronous data loading

You can load the data asynchronously to the SfDataPager by overriding the handlePageChange method and await the method while loading the data.

You can use onPageNavigationStart and onPageNavigationEnd callbacks to show and hide the loading indicator when navigating between pages.

In the below example, we have set await for 2000ms and displayed the loading indicator until 2000ms.

final OrderInfoDataSource _orderInfoDataSource = OrderInfoDataSource();

bool showLoadingIndicator = true;

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: LayoutBuilder(
      builder: (context, constraints) {
        return Row(children: [
          Column(
            children: [
              SizedBox(
                  height: constraints.maxHeight - 60,
                  width: constraints.maxWidth,
                  child: buildStack(constraints)),
              Container(
                height: 60,
                width: constraints.maxWidth,
                child: SfDataPager(
                  pageCount:
                    orders.length / rowsPerPage,
                  direction: Axis.horizontal,
                  onPageNavigationStart: (int pageIndex) {
                    setState(() {
                      showLoadingIndicator = true;
                    });
                  },
                  delegate: _orderInfoDataSource,
                  onPageNavigationEnd: (int pageIndex) {
                    setState(() {
                      showLoadingIndicator = false;
                    });
                  },
                ),
              )
            ],
          ),
        ]);
      },
    ),
  );
}

Widget buildDataGrid(BoxConstraints constraint) {
  return SfDataGrid(
      source: _orderInfoDataSource,
      columnWidthMode: ColumnWidthMode.fill,
      columns: <GridColumn>[
        GridTextColumn(
          columnName: 'orderID',
          label: Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              'Order ID',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
        GridTextColumn(
            columnName: 'customerID',
            label: Container(
              padding: EdgeInsets.symmetric(horizontal: 16.0),
              alignment: Alignment.centerLeft,
              child: Text(
                'Customer Name',
                overflow: TextOverflow.ellipsis,
              ),
            )),
        GridTextColumn(
          columnName: 'orderDate',
          label: Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              'Order Date',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
        GridTextColumn(
          columnName: 'freight',
          label: Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.center,
            child: Text(
              'Freight',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ),
      ],
    );
}

Widget buildStack(BoxConstraints constraints) {
  List<Widget> _getChildren() {
    final List<Widget> stackChildren = [];
    stackChildren.add(buildDataGrid(constraints));

    if (showLoadingIndicator) {
      stackChildren.add(Container(
        color: Colors.black12,
        width: constraints.maxWidth,
        height: constraints.maxHeight,
        child: Align(
          alignment: Alignment.center,
          child: CircularProgressIndicator(
            strokeWidth: 3,
          ),
        ),
      ));
    }

    return stackChildren;
  }

  return Stack(
    children: _getChildren(),
  );
}

class OrderInfoDataSource extends DataGridSource{
  OrderInfoDataSource() {
    paginatedOrders = orders.getRange(0, 19).toList(growable: false);
    buildPaginateDataGridRows();
  }

  List<DataGridRow> dataGridRows = [];

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

  @override
  DataGridRowAdapter? buildRow(DataGridRow row) {
    return DataGridRowAdapter(
        cells: row.getCells().map<Widget>((dataGridCell) {
      if (dataGridCell.columnName == 'orderID') {
        return Container(
          padding: EdgeInsets.symmetric(horizontal: 16.0),
          alignment: Alignment.centerRight,
          child: Text(
            dataGridCell.value.toString(),
            overflow: TextOverflow.ellipsis,
          ),
        );
      } else if (dataGridCell.columnName == 'customerID') {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerLeft,
            child: Text(
              dataGridCell.value.toString(),
              overflow: TextOverflow.ellipsis,
            ));
      } else if (dataGridCell.columnName == 'orderDate') {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.centerRight,
            child: Text(
              DateFormat.yMd().format(dataGridCell.value).toString(),
              overflow: TextOverflow.ellipsis,
            ));
      } else {
        return Container(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            alignment: Alignment.center,
            child: Text(
              NumberFormat.currency(locale: 'en_US', symbol: '\$')
                  .format(dataGridCell.value)
                  .toString(),
              overflow: TextOverflow.ellipsis,
            ));
      }
    }).toList());
  }
  
  @override
  Future<bool> handlePageChange(int oldPageIndex, int newPageIndex) async {
    int startIndex = newPageIndex * rowsPerPage;
    int endIndex = startIndex + rowsPerPage;
    if(startIndex < orders.length && endIndex <= orders.length){
      await Future.delayed(Duration(milliseconds: 2000)); 
      paginatedOrders = orders.getRange(startIndex, endIndex).toList(growable: false);
      buildPaginatedDataGridRows();
      notifyListeners();
    }else{
      paginatedOrders = [];
    }

    return true;
  }

  void buildPaginatedDataGridRows() {
    dataGridRows = paginatedOrders.map<DataGridRow>((dataGridRow) {
      return DataGridRow(cells: [
        DataGridCell(columnName: 'orderID', value: dataGridRow.orderID),
        DataGridCell(columnName: 'customerID', value: dataGridRow.customerID),
        DataGridCell(columnName: 'orderDate', value: dataGridRow.orderData),
        DataGridCell(columnName: 'freight', value: dataGridRow.freight),
      ]);
    }).toList(growable: false);
  }
}

flutter datapager with asynchronous loading

NOTE
Download demo application from GitHub.

Programmatic page navigation

The SfDataPager provides the support to navigate between the pages programmatically using controller with following options,

The following code example shows how to navigate the previous page programmatically,

@override 
  Widget build(BuildContext context) { 
    return Scaffold( 
      body: LayoutBuilder(builder: (context, constraint) { 
      return Column( 
        children: [ 
          MaterialButton( 
            onPressed: () { 
              controller.previousPage(); 
            }, 
            child: Text('Move Previous page'), 
          ), 
          SizedBox( 
              height: constraint.maxHeight - 120, 
              width: constraint.maxWidth, 
              child: _buildDataGrid()), 
          Container( 
            height: 60, 
            child: Align( 
                alignment: Alignment.center, 
                child: SfDataPager( 
                  delegate: orderInfoDataSource, 
                  initialPageIndex: index, 
                  controller: controller, 
                  pageCount: orderInfoDataSource.orders.length / 
                      orderInfoDataSource.rowsPerPage, 
                  direction: Axis.horizontal, 
                )), 
          ) 
        ], 
      ); 
    }), 
    );
  }

Orientation

SfDataPager allows you to arrange the child elements either horizontally or vertically. This can be achieved by using the direction Property. direction is an Enum type.

Enum Description
horizontal This is the default enum value for direction. Arranges all the navigation buttons and numeric buttons horizontally.

flutter datapager in horizontal direction

vertical Arranges all the navigation buttons and numeric buttons vertically by setting Axis.vertical to direction property.

flutter datapager in vertical direction

Appearance

SfDataPager allows to customize the appearance of the data pager using the SfDataPagerThemeData in SfDataPagerTheme. The SfDataPager should be wrapped inside the SfDataPagerTheme.

Import the following class from the syncfusion_flutter_core package.

import 'package:syncfusion_flutter_core/theme.dart';

The following code example illustrates using SfDataPagerThemeData with the data pager control

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfDataPagerTheme(
      data: SfDataPagerThemeData(
        itemColor: Colors.white,
        selectedItemColor: Colors.lightGreen,
        itemBorderRadius: BorderRadius.circular(5),
        backgroundColor: Colors.teal,
      ),
      child: SfDataPager(
        delegate: _orderInfoDataSource,
        pageCount: orders.length / rowsPerPage,
        direction: Axis.horizontal,
      ),
    ),
  );
}

flutter datapager with customization

Change the number of visible items (buttons) in view

You can change the number of visible items i.e. page buttons in view by using the SfDataPager.visibleItemsCount.

@override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Flutter DataGrid Sample'),
        ),
        body: LayoutBuilder(builder: (context, constraint) {
          return Column(
            children: [
              SizedBox(
                  height: constraint.maxHeight - dataPagerHeight,
                  width: constraint.maxWidth,
                  child: _buildDataGrid()),
              Container(
                height: dataPagerHeight,
                child: SfDataPager(
                  visibleItemsCount: 1,
                  delegate: orderInfoDataSource,
                  pageCount: orderInfoDataSource.orders.length /
                      orderInfoDataSource.rowsPerPage,
                  direction: Axis.horizontal,
                ),
              )
            ],
          );
        }));
  }

Load any widget in page button

You can load any widget to page button by using SfDataPager.pageItemBuilder.

@override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Flutter DataGrid Sample'),
        ),
        body: LayoutBuilder(builder: (context, constraint) {
          return Column(
            children: [
              SizedBox(
                  height: constraint.maxHeight - dataPagerHeight,
                  width: constraint.maxWidth,
                  child: _buildDataGrid()),
              Container(
                height: dataPagerHeight,
                child: SfDataPagerTheme(
                  data: SfDataPagerThemeData(
                    itemBorderColor: Colors.blue,
                    itemBorderWidth: 1,
                    backgroundColor: Colors.transparent,
                    itemBorderRadius: BorderRadius.circular(0),
                  ),
                  child: SfDataPager(
                    pageItemBuilder: (String value) {
                      return Container(
                          child: Text(
                        value,
                        style: TextStyle(
                            fontSize: 10, fontWeight: FontWeight.w700),
                      ));
                    },
                    delegate: orderInfoDataSource,
                    pageCount: orderInfoDataSource.orders.length /
                        orderInfoDataSource.rowsPerPage,
                    direction: Axis.horizontal,
                  ),
                ),
              )
            ],
          );
        }));
  }