Firestore collection query as stream in flutter
Asked Answered
M

1

7

I'm trying to query a few documents from a collection, this query should listen to changes made in the queried documents, so I'd need a stream. I'm doing following (in Dart/Flutter)

  Stream<List<MatchRequest>> _getNewMatches() {
    return Collection<MatchRequest>(path: 'requests')
        .ref
        .where('status', isNull: true)
        .where('users', arrayContains: ['$currentUid'])
        .orderBy('last_activity')
        .snapshots()
        .map((list) => list.documents.map(
            (doc) => Global.models[MatchRequest](doc.data) as MatchRequest));
  }

(The object Collection sets the path to the ref in it's constructor, eg: ref = db.collection($path) and the map makes a model of the results)

Then I'm using a StreamBuilder with stream invoking the method above and builder checking if snapshot.hasData. But it keeps loading, snapshot.hasData keeps being false. What am I doing wrong here?

EDIT:

  1. My firestore security rules contain:

    match /requests/{requestId} {
        allow read: if isLoggedIn();
        allow write: if isLoggedIn();
    }
    
  2. When removing every where and orderBy, it doesn't find anything as well. And there are documents present in the requests-collection

  3. When trying to query only 1 document as a stream from the requests-collection, he does find the result

  4. Is it because I should add indexes to my firestore indexes? But this won't solve my first problem which is that even without where and orderBy, it doesn't get any data

Mouthful answered 18/12, 2019 at 13:31 Comment(1)
Your first problem might be that on the code you shared you aren't listening to anything. You have snapshots() but no listen() after it. Being able to listen to a Firestore collection is not related to indexes, so you don't need to worry about that.Pinkney
P
13

I've written a simple example of it seems to be like what you are trying to do but are missing the listen() method:

Firestore.instance.collection('collection')
  .where('field', isEqualTo: 'value')
  .orderBy('field')
  .snapshots()
  .listen((QuerySnapshot querySnapshot){
    querySnapshot.documents.forEach((document) => print(document));
  }
);

This is just an example of how you can take the data from a Firestore Stream and use it on a StreamBuilder:

class _MyHomePageState extends State<MyHomePage> {
  Stream dataList;

  @override
  void initState() {
    dataList = Firestore.instance.collection('collection')
      .where('field', isEqualTo: 'value')
      .orderBy('field')
      .snapshots();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: StreamBuilder(
          stream: dataList,
          builder: (context, asyncSnapshot) {
            if(asyncSnapshot.hasError)
              return Text('Error: ${asyncSnapshot.error}');

            switch (asyncSnapshot.connectionState) {
              case ConnectionState.none: return Text('No data');
              case ConnectionState.waiting: return Text('Awaiting...');
              case ConnectionState.active:
                return ListView(
                  children: asyncSnapshot.data.map((document) => Text(document['value'])),
                );
              break;
              case ConnectionState.done: return ListView(
                children: asyncSnapshot.data.map((document) => Text(document['value'])),
              );
              break;
            }
            return null;
        }),
      ),
    );
  }

} 
Pinkney answered 18/12, 2019 at 15:8 Comment(16)
Hi! Indeed, while looking up I found that listen method. But I don't understand it very well. What I've done is return the snapshots() results as Stream<QuerySnapshot> and did the mapping after the snapshot.hasData check: snapshot.data.documents .map((doc) => Global.models[MatchRequest](doc.data) as MatchRequest) .toList();Mouthful
Now I get the data, no indexes needed apparantely. Just had to remove the '[]'-brackets in my array-contains as well.Mouthful
I'm a little bit in a rush, so I'll keep this for now, if you have some explanation as to why this works and how listen() works, I'd be happy to have some documentation or your explanation.Mouthful
Ah my solution doesn't listen to changes in database I see...Mouthful
The listen is effectively creating a Stream of your data changes in Firestore. Whenever there is a change you will receive a new snapshot of all the data result of your query.Pinkney
Sorry, I'm still not fully understanding it. So I do snapshots().listen((list) => list.documents.map((doc) => Global.models[MatchRequest](doc.data) as MatchRequest).toList()); This returns a StreamSubscription. I then invoke this method in my stream of the StreamBuilder? And then snapshot.data gives me the List<MatchRequest> ?Mouthful
When you do snapshots().listen() inside the listen() you will receive QuerySnapshots, like the code above. Those QuerySnapshots contain your documents. You can then use your data for whatever you want.Pinkney
You can invoke anything you want to do from inside the listen().Pinkney
Yeah not clear, The StreamBuilder needs a Stream. So the method _getNewMatches has to return a Stream. I saw you can also change the StreamSubscription in a Future. Is that the solution? listen().asFuture() and make a FutureBuilder. This is confusing. Could you possibly edit your answer so that it shows a method returning either a Stream or Future that I can use in a StreamBuilder? And with the mapping of the querySnapshots to my MatchRequest model?Mouthful
I can do that, but you don't need to use a StreamBuilder. You can just have a variable that holds your documents and shows them on your view and only do a setstate to reflect new changes on your view.Pinkney
Ok! Thank you very much for that clarification! :)Mouthful
Would you still like me to make an example with StreamBuilder or did the clarification work for you?Pinkney
If possible that would be very nice!Mouthful
I've updated the answer with a possible solution with StreamBuilder.Pinkney
Thanks, this was indeed the first solution I had. Now, this won't listen to changes to the documents right?Mouthful
I haven't been able to test it locally due to some restrictions on my network at the moment. But the StreamBuilder should do the listening for you. I would still strongly advise you to use the first solution.Pinkney

© 2022 - 2024 — McMap. All rights reserved.