Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - Receive and then modify data from Stream

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 USER'S 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.

like image 330
J. Saw Avatar asked Oct 26 '25 07:10

J. Saw


2 Answers

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.

like image 108
J. Saw Avatar answered Oct 28 '25 20:10

J. Saw


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;
});}
like image 31
Emmett Deen Avatar answered Oct 28 '25 21:10

Emmett Deen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!