36

I would like to have a listener method that checks for changes to a collection of documents if changes occur.

Something like:

import 'package:cloud_firestore/cloud_firestore.dart';


  Future<Null> checkFocChanges() async {
    Firestore.instance.runTransaction((Transaction tx) async {
      CollectionReference reference = Firestore.instance.collection('planets');
      reference.onSnapshot.listen((querySnapshot) {
        querySnapshot.docChanges.forEach((change) {
          // Do something with change
        });
      });
    });
  }

The error here is that onSnapshot isn't defined on CollectionReference.

Any ideas?

Patrioticcow
  • 26,422
  • 75
  • 217
  • 337

2 Answers2

75

Reading through cloud_firestore's documentation you can see that a Stream from a Query can be obtained via snapshots().

For you to understand, I will transform your code just a tiny bit:

CollectionReference reference = Firestore.instance.collection('planets');
reference.snapshots().listen((querySnapshot) {
  querySnapshot.documentChanges.forEach((change) {
    // Do something with change
  });
});

You should also not run this in a transaction. The Flutter way of doing this is using a StreamBuilder, straight from the cloud_firestore Dart pub page:

StreamBuilder<QuerySnapshot>(
  stream: Firestore.instance.collection('books').snapshots(),
  builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
    if (!snapshot.hasData) return new Text('Loading...');
    return new ListView(
      children: snapshot.data.documents.map((DocumentSnapshot document) {
        return new ListTile(
          title: new Text(document['title']),
          subtitle: new Text(document['author']),
        );
      }).toList(),
    );
  },
);

If you want to know any more, you can take a look at the source, it is well documented, where it is not self-explanatory.

Also take note that I changed docChanges to documentChanges. You can see that in the query_snapshot file. If you are using an IDE like IntelliJ or Android Studio, it is also pretty easy to click through the files in it.

creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
  • Thanks. My reasoning for listening for changes on `planets` is to do some logic if something changes, not just display the results in a view. – Patrioticcow May 22 '18 at 16:23
  • In `flutter` where would I put `checkForChanges()` so that it gets triggered on firebase changes? I was thinking in the `initState` override. – Patrioticcow May 22 '18 at 16:27
  • @Patrioticcow Yeah, why not? The `listen` function on the `query.snapshots()` Stream will be triggered on changes. – creativecreatorormaybenot May 22 '18 at 16:44
  • @creativecreatorormaybenot Please tell me, how to cancel this subscription? assigning it to a stream subscription and canceling it is not working, snapshots.listen() is still firing – a0x2 Nov 04 '18 at 09:13
  • 1
    this will trigger every time widget rebuilds. can you tell me how should I prevent that from happening and still be able to use snapshot listener so that I don't loose realtime update? @creativecreatorormaybenot – Manoj MM Jun 13 '19 at 12:47
  • For this use case its best to use a state manager, like provider*** that way you wont need an init state and the stream will always be flowing. and you can access the data from anywhere in you app. – CodeMemory Jul 02 '19 at 21:29
  • How do we use StreamProvider and Firestore real-time collections without refetching on every build. This pattern seems to be very costly. – SacWebDeveloper Aug 08 '19 at 20:54
  • @SacWebDeveloper It does not refresh on builds, that's the magic. `Stream`'s are not invoked, they only send data when the controller wants that and that also means that updates are only sent when there is an update in Firestore and not when your widget is rebuilt. – creativecreatorormaybenot Aug 08 '19 at 22:34
  • I mean the encapsulating build. If I had MyScreen that contained a StreamBuilder with firebase.instance.collection().snapshots(), when MyScreen calls build, shouldn't it loose hold of the snapshots and call that method again to get the stream again, in turn recreating the connection to Firestore? How does it recycle the old connection and not add to my daily read operations? – SacWebDeveloper Aug 08 '19 at 23:13
  • I moved the Stream creation to a call in didUpdateDependencies and my print statement inside my getData function only gets called once now which I believe has saved me read operations... been banging my head for days now trying to architect this is a way that is not making hundreds of useless Firestore calls. – SacWebDeveloper Aug 08 '19 at 23:16
  • @SacWebDeveloper What I was trying to say is that it does not perform useless read operations even if you use two seperate `StreamBuilder`'s. The underlying plugin handles this for you. – creativecreatorormaybenot Aug 09 '19 at 09:14
  • 1
    Does a single add operation on a collection download the entire collection (billing) or does the plugin handle it internally by only modifying the list it probably stores internally? – Mother of Stackoverflow Jul 25 '20 at 14:23
  • Well, Flutter clean architecture advices against using direct listenings to database on the ui level. – Konstantin Voronov Jan 21 '23 at 19:20
1

Using this you can listen changes inside a single document.

DocumentReference reference = FirebaseFirestore.instance.collection('collection').doc("document");
    reference.snapshots().listen((querySnapshot) {

      setState(() {
        field =querySnapshot.get("field");
      });
    });
Mahesh Phuyal
  • 21
  • 1
  • 2