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

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
SimonartM
  • 668
  • 2
  • 11
  • 26
  • 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. – J. S. Dec 18 '19 at 14:55

1 Answers1

12

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;
        }),
      ),
    );
  }

} 
J. S.
  • 8,905
  • 2
  • 34
  • 44
  • 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 and did the mapping after the snapshot.hasData check: `snapshot.data.documents .map((doc) => Global.models[MatchRequest](doc.data) as MatchRequest) .toList();` – SimonartM Dec 18 '19 at 15:10
  • Now I get the data, no indexes needed apparantely. Just had to remove the '[]'-brackets in my array-contains as well. – SimonartM Dec 18 '19 at 15:11
  • 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. – SimonartM Dec 18 '19 at 15:12
  • Ah my solution doesn't listen to changes in database I see... – SimonartM Dec 18 '19 at 15:15
  • 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. – J. S. Dec 18 '19 at 15:22
  • 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 ? – SimonartM Dec 18 '19 at 15:28
  • 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. – J. S. Dec 18 '19 at 15:30
  • You can invoke anything you want to do from inside the listen(). – J. S. Dec 18 '19 at 15:33
  • 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? – SimonartM Dec 18 '19 at 15:35
  • 1
    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. – J. S. Dec 18 '19 at 15:37
  • Ok! Thank you very much for that clarification! :) – SimonartM Dec 18 '19 at 15:39
  • Would you still like me to make an example with StreamBuilder or did the clarification work for you? – J. S. Dec 18 '19 at 15:42
  • If possible that would be very nice! – SimonartM Dec 18 '19 at 15:44
  • I've updated the answer with a possible solution with StreamBuilder. – J. S. Dec 18 '19 at 15:51
  • Thanks, this was indeed the first solution I had. Now, this won't listen to changes to the documents right? – SimonartM Dec 18 '19 at 15:55
  • 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. – J. S. Dec 18 '19 at 16:01