Populate DataGrid with Firestore stream/subscription

Hi there!

I'm trying to populate a DataGrid with data from Firestore. I also want to subscribe to the collection to listen for updates and have those updates add new rows to the DataGrid.

Unfortunately, I can't seem to get this to work.

I'm using a StreamBuilding in the build method for the page. Which populates the grid, but if I navigate back to the page I end up with duplicate entries.

body:
StreamBuilder<QuerySnapshot>(
stream: users.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}

if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}

snapshot.data.docs.forEach((element) {
          _datasource.add(element['id']; // datasource is currently a global variable. I'd rather not do that.

});

return UserDataGrid();
},
),

4 Replies 1 reply marked as answer

BS Balasubramani Sundaram Syncfusion Team January 25, 2021 12:53 PM UTC

Hi Grady,  
  
Thanks for contacting Syncfusion support,  
  
In StreamBuilder, every time when we are getting the bunch of data, already data which we got will be retrieved too. We should load a unique data into our collection while iterating the data from stream.  
  
After adding the new items into the collection, you must call the DataGridSource.notifyListener() to refresh the datagrid.  
  
  
Please refer the below code snippet and file for more reference,  
  
Code snippet [dart] 
  
  
[main.dart] 
  
@override 
  Widget build(BuildContext context) { 
    return MaterialApp( 
      title: 'Flutter Demo', 
      theme: ThemeData( 
        primarySwatch: Colors.blue, 
        visualDensity: VisualDensity.adaptivePlatformDensity, 
      ), 
      home: Scaffold( 
        appBar: AppBar( 
          title: Text('DataGrid'), 
        ), 
        body: StreamBuilder<QuerySnapshot>( 
          stream: employeeDataGridSource.getStream(), 
          builder: (context, snapshot) { 
            employeeDataGridSource.buildStream(snapshot); 
            return SfDataGrid( 
              source: employeeDataGridSource, 
              columnWidthMode: ColumnWidthMode.fill, 
              columns: [ 
                GridNumericColumn(mappingName: 'employeeID', headerText: 'ID'), 
                GridTextColumn(mappingName: 'employeeName', headerText: 'Name'), 
                GridTextColumn(mappingName: 'designation', headerText: 'Role'), 
                GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), 
              ], 
            ); 
          } 
        ), 
      ), 
    ); 
  } 
  
  
  
class EmployeeDataGridSource extends DataGridSource<Employee> { 
  CollectionReference collection; 
  
  EmployeeDataGridSource() { 
    collection = FirebaseFirestore.instance.collection('employees'); 
  } 
  
  Stream<QuerySnapshot> getStream() { 
    return collection.orderBy('employeeID', descending: false).snapshots(); 
  } 
  
  Future<Void> buildStream(AsyncSnapshot snapShot) async { 
    if (snapShot.hasError || 
        snapShot.data == null || 
        snapShot.data.docs.length == 0) { 
      return Future<Void>.value(); 
    } 
  
    await Future.forEach(snapShot.data.docs, (element) { 
      final Employee data = Employee.fromSnapshot(element); 
      if (!employees.any((element) => element.employeeID == data.employeeID)) { 
        employees.add(data); 
      } 
    }); 
  
    updateDataGridDataSource(); 
  
    return Future<Void>.value(); 
  } 
  
  void updateDataGridDataSource() { 
    notifyListeners(); 
  } 
} 
  
  
  
   
We hope that it will help you to achieve your requirement. 
  
Regards,  
Balasubramani Sundaram. 


Marked as answer

GR Grady January 25, 2021 02:09 PM UTC

Thanks so much for your help! I had completely forgotten to call updateDataGridSource.

Last night I came up with this solution:

@override
void initState() {
// fetch users collection and watch for changes
CollectionReference reference = Firestore.instance.collection('users');
reference.snapshots().listen((querySnapshot) {
querySnapshot.docChanges.forEach((change) {
DocumentSnapshot doc = change.doc;
String docId = doc.id;

Person person = new Person(
id: doc.data()['id'],
displayName: doc.data()['name'],
role: new CalendarResource(id: doc.data()['role']),
balance: doc.data()['balance']
);

switch(change.type) {
case (DocumentChangeType.added): {
print("added: " + change.doc.data().toString());
_personMap.putIfAbsent(docId, () => person);
break;
}
case (DocumentChangeType.removed): {
print("removed: " + change.doc.data().toString());
_personMap.remove(docId);
break;
}
case (DocumentChangeType.modified): {
print("modified: " + change.doc.data().toString());
_personMap.update(docId, (value) => person);
break;
}
}
_personData = _personMap.values.toList();
_personData.sort((a, b) => a.displayName.compareTo(b.displayName));
_personDataSource.updateDataGridSource();
});
});





NK Neelakandan Kannan Syncfusion Team January 27, 2021 02:55 AM UTC

Hi Grady,

Thanks for your update.

We are glad to know that your requirement has been achieved. Please let us know if you need any assistance on this.

Regards,
Neelakandan


DO Dorin replied to Grady October 11, 2023 03:30 PM UTC

Hi @Grady can you please provide a full working sample (example) to test, I tried in a lot of ways unsuccessfully :(

Thanks in a advance!


Loader.
Up arrow icon