Flutter - Receive and then modify data from Stream

1,214

Solution 1

Alright I think I found the path to the solution. I get the data from the stream, modify it, and then yield it to the StreamBuilder within one method and no longer need the FutureBuilder. The key to this, as Christopher Moore mentioned in the comment, is await for. The stream method looks like this:

  Stream<List> aStream() async* {

    List dataToReturn = List();

    Stream<QuerySnapshot> stream = Firestore.instance
        .collection(LevelOne)
        .document(OWN UID)
        .collection(LevelTwo)
        .snapshots();

    await for (QuerySnapshot q in stream){
      for (var doc in q.documents) {
        Map<String, dynamic> dataMap= Map<String, dynamic>();

        DocumentSnapshot userDoc = await Firestore.instance
            .collection('UserData')
            .document(doc['other user data var'])
            .get();

        dataMap['i'] = userDoc['i'];
        //...//
        dataToReturn.add(dataMap);
      }
      yield dataToReturn;
    }
  }

And then the StreamBuilder is populated with the modified data as I desired.

Solution 2

I found myself using this to implement a chat system using the Dash Chat package in my app. I think using the map function on a stream may be a little cleaner here is a sample:

Stream<List<ChatMessage>> getMessagesForConnection(
  String connectionId) {
return _db
    .collection('connections')
    .doc(connectionId)
    .collection('messages')
    .snapshots()
    .map<List<ChatMessage>>((event) {
  List<ChatMessage> messages = [];

  for (var doc in event.docs) {
    try {
      messages.add(ChatMessage.fromJson(doc.data()));
    } catch (e, stacktrace) {
      // do something with the error
    }
  }
  return messages;
});}
Share:
1,214
Sludge
Author by

Sludge

Hello

Updated on December 22, 2022

Comments

  • Sludge
    Sludge over 1 year

    I'm attempting to do the following:

    1. Listen to a Firestore stream so when a new document is added, the StreamBuilder will receive it, modify it, and then present it.

    The "modification" takes the Stream data, which includes a Firestore UID, gets the data from Firestore with that UID, and then the StreamBuilder is populated with that data.

    So the flow is: New document added -> Stream gets document -> Function gets UID from that document -> Function uses that UID to get more data from Firestore -> Function returns to populate StreamBuilder with that new data.

    My current set-up is as follows -- which works, but the FutureBuilder is obviously making the Firestore call each time the widget is rebuilt, and nobody wants that.

    
      Stream<QuerySnapshot> upperStream;
    
      void initState() {
        super.initState();
        upperStream = aStream();
    }
    
      Stream<QuerySnapshot> aStream() {
        return Firestore.instance
            .collection('FirstLevel')
            .document(ownUID (not related to stream))
            .collection('SecondLevel')
            .snapshots();
      }
    
      Future<List> processStream(List streamData) async {
        List futureData = List();
    
        for (var doc in streamData) {
          Map<String, dynamic> dataToReturn = Map<String, dynamic>();
          DocumentSnapshot userDoc = await Firestore.instance
              .collection('FirstLevel')
              .document(OTHER USER'S UID FROM STREAM)
              .get();
    
          dataToReturn['i'] = userDoc['i'];
    
          futureData.add(dataToReturn);
        }
        return futureData;
      }
    ...
    ...
    //The actual widget
            Expanded(
              child: StreamBuilder(
                  stream: upperStream,
                  builder: (context, snapshot) {
                    // Error/null handling
                    return FutureBuilder(
                        future: processStream(snapshot.data.documents),
                        builder: (context, futureSnap) {
                         // Error/null handling
                          return ListView.builder(
                              shrinkWrap: true,
                              itemCount: futureSnap.data.length,
                              scrollDirection: Axis.vertical,
                              itemBuilder: (context, index) {
                               //Continuing with populating
                              });
                        });
                  }),
            ),
    
    

    What's the best way to handle a flow like this? Creating a method where the data from the Firestore stream is modified and then returned without needing ListView.builder at all?

    Edit: I tried creating my own stream like this:

      Stream<Map<String, dynamic>> aStream2() async* {
    
        QuerySnapshot snap = await Firestore.instance
            .collection(FirstLevel)
            .document(OWN UID)
            .collection(SecondLevel)
            .getDocuments();
    
        for (var doc in snap.documents) {
          Map<String, dynamic> data = Map<String, dynamic>();
          DocumentSnapshot userDoc = await Firestore.instance
              .collection(FirstLevel)
              .document(OTHER USERS UID RECEIVED FROM STREAM)
              .get();
    
          data['i'] = userDoc['i'];
    
          yield data;
        }
      }
    

    However, the Stream is not triggered/updated when a new Document is added to the SecondLevel collection.