flutterfirebasegoogle-cloud-firestoredisposestream-builder

Firestore: How does StreamBuilder work and how to use it efficiently


I want to know:

Suppose I have a streamBuilder in an application like Chat, so if a user is not on the screen that has the streamBuilder and a new doc is added would it automatically count as a read?

How to properly use StreamBuilder and then Close it when the user leaves the screen for consuming the least amount of reads.

Thank You


Solution

  • By definition :

    A StreamBuilder is a widget that builds its output based on a stream of data. The stream can be anything that emits data over time, such as a Firestore collection. When the stream emits new data, the StreamBuilder will rebuild its output to reflect the new data.

    The StreamBuilder takes two arguments: the stream and a builder function. The builder function will be called whenever the stream emits new data.

    The following code shows how to use a StreamBuilder to display a list of Firestore documents:

    class _HomePageState extends State<HomePage> {
      Stream<QuerySnapshot> stream; // Declaring Stream 
    
      @override
      void initState() {
        super.initState();
    
        stream = FirebaseFirestore.instance
            .collection('documents')
            .snapshots();
      }
    
      @override
      void dispose() {
    // To close a StreamBuilder, you can use the dispose() method. 
        stream.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('StreamBuilder with Debounce and Throttle'),
          ),
          body: StreamBuilder<QuerySnapshot>(
            stream: stream,
            builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
             if (snapshot.hasError) return Text('Something went wrong');
            if (snapshot.connectionState == ConnectionState.waiting) return Text("Loading");
            List<DocumentSnapshot> documents = snapshot.data!.docs;
            return ListView.builder(
                itemCount: documents.length,
                itemBuilder: (BuildContext context, int index) {
                  DocumentSnapshot document = documents[index];
                  return ListTile(
                    title: Text(document['title']),
                    subtitle: Text(document['body']),
                  );
                },
              );
            },
          ),
        );
      }
    }
    

    TLDR : Do some research on your app. Do you really need real-time updates of documents if you do then handle those StreamBuilders cautiously and make sure to dispose of those streams once the user navigates to the new screen.

    Update : When using streamController we also need to dispose it using StreamSubscription.cancel() or calling streamController.cancel() as follows:

    class _MyWidgetState extends State<MyWidget> {
      final StreamController<DocumentSnapshot> _streamController =
          StreamController.broadcast(); 
    
      StreamSubscription _subscription;
    
      @override
      void initState() {
        super.initState();
    
        // Listen to the stream and update the UI when a new document is added.
        _streamController.stream.listen((document) {
          setState(() {
            // Update the UI here.
          });
        },
        onDispose: () {
          // Dispose of the stream controller when the subscription is disposed.
          _streamController.dispose();
        });
    
      }
    
      @override
      void dispose() {
        // Dispose of the stream controller when the widget is disposed.
        _streamController.dispose(); // when used just StreamController
        _subscription.cancel(); // when used StreamSubscription 
        super.dispose();
      }
    }
    

    Reference :

    1. One-time Read
    2. Realtime changes
    3. StreamBuilder class