Flutter StreamBuilder for multiple firebase documents

Viewed 476

I am trying to make my Flutter app update when a change is made to the usersCollection.document(user.uid) firebase document.

When the user document is updated I want to retrieve the data from this document but also from another firebase document, facilitiesCollection.document(...).

My current code

Future<Map> _getCheckedInFacilityData() async {
  Map<String, dynamic> result = {};

  try {
    DocumentSnapshot userDoc =
        await _db.usersCollection.document(user.uid).get();

    if (userDoc.data['checkedIn']) {
      // User is checked in
      DocumentSnapshot facDoc = await _db.facilitiesCollection
          .document(userDoc.data['activeFacilityID'].toString())
          .get();

      result['facilityID'] = userDoc.data['activeFacilityID'];
      result['sessionID'] = userDoc.data['activeSessionID'];
      result['facilityActiveUsers'] = facDoc.data['activeUsers'].length;
      result['facilityName'] = facDoc.data['name'];

      return result;
    }
  } catch (er) {
    debugPrint(er.toString());
  }
  return null;
}
FutureBuilder<Map>(
  future: _getCheckedInFacilityData(),
  builder: (context, map) {
    switch (map.connectionState) {
      case ConnectionState.waiting:
        return Center(child: CircularProgressIndicator());
...

This is currently working but the page is not updated when a change is made to the user document. I haven't been using Flutter/Dart for long so any ideas are welcome.

Is it possible to return a custom object/map which is comprised of 2 separate documents from a StreamBuilder, or is there another method that will work in my situation.

1 Answers

Surely you can do it with Streams asyncMap() and then listen in StreamBuilder

Basic algoritm

Get stream of you first data type and then asyncMap to wait second data type and return them both

stream.asyncMap(
  (v1) async {
    final v2 = await Future.delayed(Duration(seconds: 1), () => 4);
    return v1 * v2;
  },
);

Closer to your code

Stream<Map<String, dynamic>> _getCheckedInFacilityData() {

  return _db.usersCollection.document(user.uid).snapshots()
      .asyncMap(
    (userDoc) async {
      final DocumentSnapshot facDoc =
          await _db.facilitiesCollection
      .document(userDoc.data['activeFacilityID'].toString())
      .get();
      final Map<String, dynamic> userMap = userDoc.data;
      final Map<String, dynamic> facMap = facDoc.data;
      return userMap..addAll(facMap);
    },
  );
}

In this function I merge two maps - be carefull if both maps have identical keys map will keep only last was added key in our case from addAll(facMap)

Last step is to show you streamed data on screen - use StreamBuilder

StreamBuilder<Map>(
  stream: _getCheckedInFacilityData(),
    builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Text('${snapshot.error}');
          } else if (snapshot.connectionState == ConnectionState.waiting) {
            return LinearProgressIndicator();
          }
          
          return /* some widget that shows your data*/;
        },
      ),
Related