Shashi Kiran
Shashi Kiran

Reputation: 1079

Stop listening to snapshot updates in cloud firestore in flutter

I want to stop listening to snapshot updates. The snapshot keeps listening to updates even after the screen is closed. I am using the below code to listen to the updates.

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

Upvotes: 12

Views: 16448

Answers (6)

Georg
Georg

Reputation: 1007

Since this is relating to Flutter and therefore probably to widgets changing on new snapshot events, please consider using StreamBuilder to easily delegate stream management and build great reactive UIs.

Container(
    alignment: FractionalOffset.center,
    color: Colors.white,
    child: StreamBuilder<int>(
      stream: _bids,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        List<Widget> children;
        if (snapshot.hasError) {
          children = <Widget>[
            const Icon(
              Icons.error_outline,
            ),
            Padding(
              padding: const EdgeInsets.only(top: 16),
              child: Text('Error: ${snapshot.error}'),
            ),
          ];
        } else {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
              children = const <Widget>[
                Icon(
                  Icons.info,
                ),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: Text('Select a lot'),
                )
              ];
              break;
            case ConnectionState.waiting:
              children = const <Widget>[
                SizedBox(
                  width: 60,
                  height: 60,
                  child: CircularProgressIndicator(),
                ),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: Text('Awaiting bids...'),
                )
              ];
              break;
            case ConnectionState.active:
              children = <Widget>[
                const Icon(
                  Icons.check_circle_outline,
                  color: Colors.green,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('\$${snapshot.data}'),
                )
              ];
              break;
            case ConnectionState.done:
              children = <Widget>[
                const Icon(
                  Icons.info,
                  color: Colors.blue,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('\$${snapshot.data} (closed)'),
                )
              ];
              break;
          }
        }

        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: children,
        );
      },
    )

Or just use snapshot.hasData and snapshot.hasError to switch between showing Circle, error, and actual data.

Upvotes: 1

Knight Ramsamy
Knight Ramsamy

Reputation: 112

For me, I simply use take on the snapshot method. Basically, the same principle as rxjs with Firebase. You take one stream of data and stop listening.

return collection
        .withConverter<Discount>(
          fromFirestore: (snapshots, _) => Discount.fromMap(snapshots
              .data()!),
          toFirestore: (discount, _) => discount
              .toMap(),
        )
        .snapshots()
        .take(1); // Take 1 to stop listening to events

If you run and print out the ConnectionState, you'll see it goes to Done once it fetches all the docs in the collection.

Upvotes: 0

Moyebang
Moyebang

Reputation: 11

the correct way is stream.pause() so the listener will be on pause mode cancel() destroy the listener and his content

Upvotes: 0

JLP-Dev
JLP-Dev

Reputation: 265

Very late answer, but I thought I'd complete the previous answers with a code sample as it might be useful to others.

class EventsScreen extends StatefulWidget {
  EventsScreen({Key key}) : super(key: key);

  @override
  _EventsScreenState createState() => _EventsScreenState();
}

class _EventsScreenState extends State<EventsScreen> {
  StreamSubscription<QuerySnapshot> _eventsSubscription;

  @override
  void initState() {
    // Initialise your stream subscription once
    CollectionReference eventsReference = Firestore.instance.collection('Events');
    _eventsSubscription = eventsReference.snapshots().listen((snapshot) => _onEventsSnapshot);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // Build your widget here
    return Container();
  }

  void _onEventsSnapshot(QuerySnapshot snapshot) {
    // Remove the setState() call if you don't want to refresh your screen whenever you get a fresh snapshot
    setState(() {
      snapshot?.documentChanges?.forEach(
        (docChange) => {
          // If you need to do something for each document change, do it here.
        },
      );
      // Anything you might do every time you get a fresh snapshot can be done here.
    });
  }

  @override
  void dispose() {
    // Cancel your subscription when the screen is disposed
    _eventsSubscription?.cancel();
    super.dispose();
  }
}

This approach leverages the StatefulWidget's state to handle documents changes.

A better approach would be to use a Provider or the BLoC pattern so that the subscription to your Firestore is not created and handled in the UI.

Upvotes: 4

Shady Aziza
Shady Aziza

Reputation: 53317

Your listener is of type StreamSubscription, so you can call some helpful methods on your listener such as cancel()

    CollectionReference reference = Firestore.instance.collection('Events');
StreamSubscription<QuerySnapshot> streamSub = reference.snapshots().listen((querySnapshot) {
  querySnapshot.documentChanges.forEach((change) {
    // Do something with change
  });
});
//somewhere
streamSub.cancel();

Upvotes: 28

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277047

The listen method returns a Subscription

This class is used to cancel the listening.

You should store inside your state that object to cancel subscription on dispose.

Upvotes: 6

Related Questions