StreamBuilder limitation

3,923

It seems like the actual problem based on your comments above is that it crashes after you navigate away from the view using the stream. You have to either:

  • Cancel your stream controller when you navigate away so that it's not listening for any more events.
  • Or just don't emit any new values through the stream after navigation. Add a pause on it until you come back to the view

Update: Adding code with pseudo example

class Widget {
  // Your local stream 
  Stream<String> _localStream;
  // Value to indicate if you have navigated away
  bool hasNavigated = false;
  ...
  void init() {
    // subscribe to the firebase stream
    firebaseStream...listen((value){
      // If this value is still false then emit the same value to the localStream
      if(!hasNavigated) {
        _localStream.add(value);
      }
    });
  }

  Widget build() {
    return StreamBuilder(
      // subscribe to the local stream NOT the firebase stream
      stream: _localStream,
      // handle the same way as you were before
      builder: (context, snapshot) {
         return YourWidgets();
      }
    );
  }
}
Share:
3,923
FlutterFirebase
Author by

FlutterFirebase

Updated on December 09, 2022

Comments

  • FlutterFirebase
    FlutterFirebase over 1 year

    StreamBuilder is rebuild whenever it get new event. This cause problem with for example navigation (Navigator.push) because if new event is receive while navigate then this trigger rebuild. Because try to navigate while widget tree still being built, this will throw error.

    It is not possible to prevent rebuild to avoid this issue as required.

    Suggested workaround is basically take stream from cache. Also: here and here

    But this mean cannot have StreamBuilder build list which constantly update if also want to provide navigation from cards on list. For example in card onPressed(). See here.

    So to refresh data must use pull to refresh…

    Anyone have better solution? Or is Flutter team work on solve this limitation for example by allow prevent rebuild if card is tap by user?

    UPDATE:

    TL;DR Is pull to refresh only way to update data since stream in StreamBuilder must be cached to prevent it rebuilding every time new event is received?

    UPDATE 2:

    I have try implement cache data but my code not work:

    Stream<QuerySnapshot> infoSnapshot;
    
    fetchSnapshot()  {
      Stream<QuerySnapshot> infoSnapshot = Firestore.instance.collection(‘info’).where(‘available’, isEqualTo: true).snapshots();
      return infoSnapshot;
    }
    
    
      @override
      void initState() {
        super.initState();
      fetchSnapshot();
      }
    

    ...

    child: StreamBuilder(
    stream: infoSnapshot,
    builder: (context, snapshot) {
    
    if(snapshot.hasData) {
       return ListView.builder(
            itemBuilder: (context, index) =>
                build(context, snapshot.data.documents[index]),
            itemCount: snapshot.data.documents.length,
         );
      } else {
          return _emptyStateWidget();
      }
    

    UPDATE 3:

    I have try use StreamController but cannot implement correct:

    Stream<QuerySnapshot> infoStream;
    StreamController<QuerySnapshot> infoStreamController = StreamController<QuerySnapshot>();
    
      @override
      void initState() {
        super.initState();
    
      infoStream = Firestore.instance.collection(‘info’).where(‘available’, isEqualTo: true).snapshots();
      infoStreamController.addStream(infoStream);
      }
    

    child: StreamBuilder(
    stream: infoStreamController.stream,
    builder: (context, snapshot) {
    

    UPDATE 4:

    Suggestion to use _localStreamController give error:

    StreamController<QuerySnapshot> _localStreamController = StreamController<QuerySnapshot>();
    
      @override
      void initState() {
        super.initState();
    
    Firestore.instance.collection(‘info’).snapshots().listen((QuerySnapshot querySnapshot) {
    
    //      if(userAdded == null) {
            _localStreamController.add(querySnapshot);
    //      }
    
        });
    ...
    child: StreamBuilder(
    stream: _localStreamController.stream,
    builder: (context, snapshot) {
    

    The getter 'stream' was called on null.

    The method 'add' was called on null.

  • FlutterFirebase
    FlutterFirebase about 5 years
    My stream is from Firestore so I not think you answer is possible
  • Filled Stacks
    Filled Stacks about 5 years
    @FlutterFirebase It's definitely possible. Subscribe to the fluter stream locally in your stateful view, transform and emit the values that come through to a local stream in your state. Use the local stream in the widget builder, not the firestore state. When you navigate away cancel the subscription of your local stream that you setup. Then you won't get any more values.
  • FlutterFirebase
    FlutterFirebase about 5 years
    Thanks for reply! I am try implement you cache now but cannot make work. I have update question with my code
  • Filled Stacks
    Filled Stacks about 5 years
    @FlutterFirebase That's not a cache implementation and also, you don't need to cache anything. You just add a step in between using the firebase stream as is :/ Don't have time to type up code now. I'll come back to this later and write pseudo code to help you understand how you can solve it.
  • FlutterFirebase
    FlutterFirebase about 5 years
    Thanks! I look but cannot find how
  • FlutterFirebase
    FlutterFirebase about 5 years
    I have try use StreamController to get Firestore stream local like you say but cannot make work. I have update question with code
  • Filled Stacks
    Filled Stacks about 5 years
    @FlutterFirebase I think you're miss understanding what I was saying. You need to call .listen on your firebase stream. You'll use the values that's broadcast to your listen callback to send that through to your localStream. The local stream goes into the StreamBuilder, NOT the firebase stream. Listen callback will be guarded by some value indicating if you've navigated away, while it's false you emit values to your local stream. When you navigate away and set your guardValue to true then it won't emit any values to the localStream until you come back to the view.
  • Filled Stacks
    Filled Stacks about 5 years
    @FlutterFirebase that's the best I can do with the code now. I have to release an app today. That's all pseudo code but should give you and idea of what to do.
  • FlutterFirebase
    FlutterFirebase about 5 years
    Thanks for post! I try implement but very difficult. Cannot call .add() on Stream. I can only find possible to call on StreamController like in Update 3. But you say this not correct approach. Is possible give more code?
  • Filled Stacks
    Filled Stacks about 5 years
    @flutterfirebase put your stream in a stream controller. It's Pseudo code. It's not actual real code, you should interpret it and write the real code. Just add the local stream into a stream controller and call add on that.
  • FlutterFirebase
    FlutterFirebase about 5 years
    I really try but cannot make work because cannot cancel StreamController. So it keep on listen to stream and no difference to original code.
  • FlutterFirebase
    FlutterFirebase about 5 years
    I have post update 4 show implement in real code you suggestion give error
  • Filled Stacks
    Filled Stacks about 5 years
    @FlutterFirebase all of the best to you man. I gave you a solution, I'm definitely not writing the code for you. You should be able to handle null references on your own :). Good luck