Search code examples
flutterdartreactive-programming

How to rebuild StreamBuilder on resumed state with Flutter


I develop an app using BLoC pattern. In my app there are 2 routes, route A and B, and both of them access same data. A problem caused when moving the routes as below.

  1. Move to route B from route A that shows the data.
  2. Update the data at route B.
  3. Back to route A.

After moving back to route A, the StreamBuilder of showing the data never updates automatically. How can I let the StreamBuilder update on resumed state?

Here are sample codes.

routeA.dart

class RouteA extends StatefulWidget {
  @override
  _RouteAState createState() => _RouteAState();
}

class _RouteAState extends State<RouteA> {
  @override
  Widget build(BuildContext context) {
    final bloc = Bloc();
    return Column(
      children: [
        StreamBuilder(    // this StreamBuilder never updates on resumed state
            stream: bloc.data, // mistake, fixed. before: bloc.count
            builder: (_, snapshot) => Text(
                  snapshot.data ?? "",
                )),
        RaisedButton(
          child: Text("Move to route B"),
          onPressed: () {
            Navigator.of(context).pushNamed("routeB");
          },
        ),
      ],
    );
  }
}

routeB.dart

class RouteB extends StatefulWidget {
  @override
  _RouteBState createState() => _RouteBState();
}

class _RouteBState extends State<RouteB> {
  @override
  Widget build(BuildContext context) {
    final bloc = Bloc();
    return Center(
      child: RaisedButton(
        child: Text("Update data"),
        onPressed: () {
          bloc.update.add(null);
        },
      ),
    );
  }
}

bloc.dart

class Bloc {
  Stream<String> data;
  Sink<void> update;
  Model _model;
  
  Bloc() {
    _model = Model();
    
    final update = PublishSubject<void>();
    this.update = update;
    
    final data = BehaviorSubject<String>(seedValue: "");
    this.data = data;
    
    update.map((event) => _model.update()).listen((event) => data.sink.add(_model.getData()));
  }
}

model.dart

class Model {
  static Model _model;

  factory Model() {    // model is singleton.
    _model ??= Model._();
    return _model;
  }

  Model._();

  int _data = 0;

  void update() {
    _data++;
  }

  String getData() {
    return _data.toString();
  }
}

Solution

  • StreamBuilder updates the data whenever it gets changed not when just by calling stream

    RouteA

    class RouteA extends StatefulWidget {
      @override
      _RouteAState createState() => _RouteAState();
    }
    
    class _RouteAState extends State<RouteA> {
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            StreamBuilder(    // this StreamBuilder never updates on resumed state
                stream: bloc.data, // mistake, fixed. before: bloc.count
                builder: (_, snapshot) => Text(
                  snapshot.data ?? "",
                )),
            RaisedButton(
              child: Text("Move to route B"),
              onPressed: () {
                Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
                  return RouteB();
                }));
              },
            ),
          ],
        );
      }
    }
    

    Route B

    class RouteB extends StatefulWidget {
      @override
      _RouteBState createState() => _RouteBState();
    }
    
    class _RouteBState extends State<RouteB> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text("Update data"),
            onPressed: () {
              bloc.updateData();
            },
          ),
        );
      }
    }
    

    Bloc

    class Bloc {
      final _update = PublishSubject<String>();
      Model _model = Model();
      Stream<String> get data => _update.stream;
    
      void updateData() async {
        _model.update();
        _update.sink.add(_model.getData());
    
        _update.stream.listen((event) {
          print(event);
        });
      }
    
      dispose() {
        _update.close();
      }
    }
    
    final bloc = Bloc();
    

    just follow above changes, it will do the trick for you.