syncfusion / flutter-widgets

Syncfusion Flutter widgets libraries include high quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base.
1.58k stars 773 forks source link

[DataGrid] high-frequency/real-time updates sample #1434

Closed TWY90 closed 8 months ago

TWY90 commented 1 year ago

Hi,

the website of sf say that datagrid has been optimized to handle high-frequency, real-time updates, and show the demo updating stock list in very fast speed. can sf provide the example code for the demo? for example how is the datagrid find the target stock ID/ index in table to update the specific row.

Thanks.

ashok-kuvaraja commented 1 year ago

Hi @TWY90 ,

The DataGrid will not recognize and refresh a particular cell by default whenever it changes. However, the DataGrid provides support for refreshing a specific cell based on its row and column indexes by using DataGridSource.notifyDataGridSourceListener with the rowColumnIndex property. You will need to determine the respective cell's row and column index and then manually call the notifyListener method with these indexes. For more information on this topic, please refer to the following User Guide documentation. It explains how to bind and manipulate data with the SfDataGrid:

Data Manipulation in Flutter DataGrid - SfDataGrid

For your reference, we have implemented a DataGrid sample that dynamically change the cell values using a Timer. We hope it will help you clarify your doubts.

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

import 'dart:async';
import 'dart:math';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Syncfusion DataGrid Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  late Timer timer;

  late StockDataGridSource stockDataGridSource;

  @override
  void initState() {
    super.initState();

    stockDataGridSource = StockDataGridSource();

    timer = Timer.periodic(const Duration(milliseconds: 200), (Timer args) {
      stockDataGridSource.timerTick(args);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SfDataGrid - Realtime update'),),
      body: Card(
        child: SfDataGrid(
          source: stockDataGridSource,
          columnWidthMode: ColumnWidthMode.none,
          columns: <GridColumn>[
            GridColumn(
              columnName: 'stock',
              label: Container(
                alignment: Alignment.center,
                child: const Text('Stock'),
              ),
            ),
            GridColumn(
              columnName: 'open',
              label: Container(
                alignment: Alignment.center,
                child: const Text(' Open'),
              ),
            ),
            GridColumn(
              columnName: 'previousClose',
              label: Container(
                alignment: Alignment.center,
                child: const Text('Previous Close'),
              ),
            ),
            GridColumn(
              columnName: 'lastTrade',
              label: Container(
                alignment: Alignment.center,
                child: const Text('Last Trade'),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    timer.cancel();
  }
}

class StockDataGridSource extends DataGridSource {
  StockDataGridSource() {
    _stocks = _getStocks(50);
    buildDataGridRows();
  }

  final Random _random = Random();
  List<Stock> _stocks = <Stock>[];

  List<DataGridRow> _dataGridRows = <DataGridRow>[];

  void timerTick(Timer args) {
    refreshRows(100);
  }

  void refreshRows(int count) {
    if (_stocks.length < count) {
      count = _stocks.length;
    }

    for (int i = 0; i < count; ++i) {
      final int recNo = _random.nextInt(_stocks.length);

      // Reinitialize the DataGridRow for particular row and call the notify to
      // view the realtime changes in DataGrid.
      void updateDataRow() {
        _dataGridRows[recNo] = DataGridRow(cells: <DataGridCell>[
          DataGridCell<double>(
              columnName: 'stock', value: _stocks[recNo].stock),
          DataGridCell<double>(columnName: 'open', value: _stocks[recNo].open),
          DataGridCell<double>(
              columnName: 'previousClose', value: _stocks[recNo].previousClose),
          DataGridCell<int>(
              columnName: 'lastTrade', value: _stocks[recNo].lastTrade),
        ]);
      }

      _stocks[recNo].stock =
          _stocksData[_random.nextInt(_stocksData.length - 1)];
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 0));
      _stocks[recNo].open = 50.0 + _random.nextInt(40);
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 1));
      updateDataRow();
      _stocks[recNo].previousClose = 50.0 + _random.nextInt(30);
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 2));
      _stocks[recNo].lastTrade = 50 + _random.nextInt(20);
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 3));
    }
  }

  void buildDataGridRows() {
    _dataGridRows = _stocks.map<DataGridRow>((Stock stock) {
      return DataGridRow(cells: <DataGridCell>[
        DataGridCell<double>(columnName: 'stock', value: stock.stock),
        DataGridCell<double>(columnName: 'open', value: stock.open),
        DataGridCell<double>(
            columnName: 'previousClose', value: stock.previousClose),
        DataGridCell<int>(columnName: 'lastTrade', value: stock.lastTrade),
      ]);
    }).toList(growable: false);
  }

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

  @override
  DataGridRowAdapter buildRow(DataGridRow row) {
    return DataGridRowAdapter(cells: <Widget>[
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[0].value.toString()),
      ),
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[1].value.toString()),
      ),
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[2].value.toString()),
      ),
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[3].value.toString()),
      ),
    ]);
  }

  void updateDataSource({required RowColumnIndex rowColumnIndex}) {
    notifyDataSourceListeners(rowColumnIndex: rowColumnIndex);
  }

  // Data set for stock data collection
  final List<double> _stocksData = <double>[
    -0.76,
    0.3,
    0.42,
    0.12,
    0.55,
    -0.78,
    0.68,
    0.99,
    0.31,
    -0.8,
    -0.99,
    0.43,
    -0.5,
    0.18,
    -0.71,
    -0.94
  ];

  final List<String> _symbols = <String>[
    'OJEC',
    'PUYU',
    'EXTB',
    'QBLI',
    'SFIO',
    'MIXR',
    'KQOW',
    'DSHN',
    'ZATR',
    'SFBK',
    'FLRT',
    'PHKH',
  ];

  List<Stock> _getStocks(int count) {
    final List<Stock> stockData = <Stock>[];
    for (int i = 1; i < _symbols.length; i++) {
      stockData.add(Stock(
          _symbols[i],
          _stocksData[_random.nextInt(_stocksData.length - 1)],
          50.0 + _random.nextInt(40),
          50.0 + _random.nextInt(30),
          50 + _random.nextInt(20)));
    }
    return stockData;
  }
}

class Stock {
  Stock(this.symbol, this.stock, this.open, this.previousClose, this.lastTrade);

  String symbol;

  double stock;

  double open;

  double previousClose;

  int lastTrade;
}

Regards, Ashok K

Trung15010802 commented 10 months ago

Hi @TWY90 ,

The DataGrid will not recognize and refresh a particular cell by default whenever it changes. However, the DataGrid provides support for refreshing a specific cell based on its row and column indexes by using DataGridSource.notifyDataGridSourceListener with the rowColumnIndex property. You will need to determine the respective cell's row and column index and then manually call the notifyListener method with these indexes. For more information on this topic, please refer to the following User Guide documentation. It explains how to bind and manipulate data with the SfDataGrid:

Data Manipulation in Flutter DataGrid - SfDataGrid

For your reference, we have implemented a DataGrid sample that dynamically change the cell values using a Timer. We hope it will help you clarify your doubts.

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

import 'dart:async';
import 'dart:math';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Syncfusion DataGrid Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  late Timer timer;

  late StockDataGridSource stockDataGridSource;

  @override
  void initState() {
    super.initState();

    stockDataGridSource = StockDataGridSource();

    timer = Timer.periodic(const Duration(milliseconds: 200), (Timer args) {
      stockDataGridSource.timerTick(args);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SfDataGrid - Realtime update'),),
      body: Card(
        child: SfDataGrid(
          source: stockDataGridSource,
          columnWidthMode: ColumnWidthMode.none,
          columns: <GridColumn>[
            GridColumn(
              columnName: 'stock',
              label: Container(
                alignment: Alignment.center,
                child: const Text('Stock'),
              ),
            ),
            GridColumn(
              columnName: 'open',
              label: Container(
                alignment: Alignment.center,
                child: const Text(' Open'),
              ),
            ),
            GridColumn(
              columnName: 'previousClose',
              label: Container(
                alignment: Alignment.center,
                child: const Text('Previous Close'),
              ),
            ),
            GridColumn(
              columnName: 'lastTrade',
              label: Container(
                alignment: Alignment.center,
                child: const Text('Last Trade'),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    timer.cancel();
  }
}

class StockDataGridSource extends DataGridSource {
  StockDataGridSource() {
    _stocks = _getStocks(50);
    buildDataGridRows();
  }

  final Random _random = Random();
  List<Stock> _stocks = <Stock>[];

  List<DataGridRow> _dataGridRows = <DataGridRow>[];

  void timerTick(Timer args) {
    refreshRows(100);
  }

  void refreshRows(int count) {
    if (_stocks.length < count) {
      count = _stocks.length;
    }

    for (int i = 0; i < count; ++i) {
      final int recNo = _random.nextInt(_stocks.length);

      // Reinitialize the DataGridRow for particular row and call the notify to
      // view the realtime changes in DataGrid.
      void updateDataRow() {
        _dataGridRows[recNo] = DataGridRow(cells: <DataGridCell>[
          DataGridCell<double>(
              columnName: 'stock', value: _stocks[recNo].stock),
          DataGridCell<double>(columnName: 'open', value: _stocks[recNo].open),
          DataGridCell<double>(
              columnName: 'previousClose', value: _stocks[recNo].previousClose),
          DataGridCell<int>(
              columnName: 'lastTrade', value: _stocks[recNo].lastTrade),
        ]);
      }

      _stocks[recNo].stock =
          _stocksData[_random.nextInt(_stocksData.length - 1)];
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 0));
      _stocks[recNo].open = 50.0 + _random.nextInt(40);
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 1));
      updateDataRow();
      _stocks[recNo].previousClose = 50.0 + _random.nextInt(30);
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 2));
      _stocks[recNo].lastTrade = 50 + _random.nextInt(20);
      updateDataRow();
      updateDataSource(rowColumnIndex: RowColumnIndex(recNo, 3));
    }
  }

  void buildDataGridRows() {
    _dataGridRows = _stocks.map<DataGridRow>((Stock stock) {
      return DataGridRow(cells: <DataGridCell>[
        DataGridCell<double>(columnName: 'stock', value: stock.stock),
        DataGridCell<double>(columnName: 'open', value: stock.open),
        DataGridCell<double>(
            columnName: 'previousClose', value: stock.previousClose),
        DataGridCell<int>(columnName: 'lastTrade', value: stock.lastTrade),
      ]);
    }).toList(growable: false);
  }

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

  @override
  DataGridRowAdapter buildRow(DataGridRow row) {
    return DataGridRowAdapter(cells: <Widget>[
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[0].value.toString()),
      ),
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[1].value.toString()),
      ),
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[2].value.toString()),
      ),
      Container(
        alignment: Alignment.center,
        child: Text(row.getCells()[3].value.toString()),
      ),
    ]);
  }

  void updateDataSource({required RowColumnIndex rowColumnIndex}) {
    notifyDataSourceListeners(rowColumnIndex: rowColumnIndex);
  }

  // Data set for stock data collection
  final List<double> _stocksData = <double>[
    -0.76,
    0.3,
    0.42,
    0.12,
    0.55,
    -0.78,
    0.68,
    0.99,
    0.31,
    -0.8,
    -0.99,
    0.43,
    -0.5,
    0.18,
    -0.71,
    -0.94
  ];

  final List<String> _symbols = <String>[
    'OJEC',
    'PUYU',
    'EXTB',
    'QBLI',
    'SFIO',
    'MIXR',
    'KQOW',
    'DSHN',
    'ZATR',
    'SFBK',
    'FLRT',
    'PHKH',
  ];

  List<Stock> _getStocks(int count) {
    final List<Stock> stockData = <Stock>[];
    for (int i = 1; i < _symbols.length; i++) {
      stockData.add(Stock(
          _symbols[i],
          _stocksData[_random.nextInt(_stocksData.length - 1)],
          50.0 + _random.nextInt(40),
          50.0 + _random.nextInt(30),
          50 + _random.nextInt(20)));
    }
    return stockData;
  }
}

class Stock {
  Stock(this.symbol, this.stock, this.open, this.previousClose, this.lastTrade);

  String symbol;

  double stock;

  double open;

  double previousClose;

  int lastTrade;
}

Regards, Ashok K

If you add SfDataPager realtime update doesn't work. I believe it's a bug

ashok-kuvaraja commented 8 months ago

Hi @TWY90,

We are glad to inform you that the issue " The values in the DataGrid didn't refresh when calling notifyDataSourceListeners alongside DataPager" has been fixed and included in our weekly pub release. Please update the DataGrid package to version 24.1.43.

https://pub.dev/packages/syncfusion_flutter_datagrid

We suspect that the reported problem has been resolved in the DataGrid package version 24.1.43. We are closing this issue. Please let us know if you have any further queries on this. We are happy to help you.

Regards, Ashok K