Search code examples
iosmobiledartflutterrxdart

Navigating to a new screen when stream value in BLOC changes


In Flutter how would I call Navigator.push when the value of a stream changes? I have tried the code below but get an error.

StreamBuilder(
        stream: bloc.streamValue,
        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
          if (snapshot.hasData && snapshot.data == 1) {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SomeNewScreen()),
            );
          }

          return Text("");
        });

enter image description here


Solution

  • You should not use StreamBuilder to handle navigation. StreamBuilder is used to build the content of a screen and nothing else.

    Instead, you will have to listen to the stream to trigger side-effects manually. This is done by using a StatefulWidget and overriding initState/dispose as such:

    class Example extends StatefulWidget {
      final Stream<int> stream;
    
      const Example({Key key, this.stream}) : super(key: key);
    
      @override
      ExampleState createState() => ExampleState();
    }
    
    class ExampleState extends State<Example> {
      StreamSubscription _streamSubscription;
    
      @override
      void initState() {
        super.initState();
        _listen();
      }
    
      @override
      void didUpdateWidget(Example oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (oldWidget.stream != widget.stream) {
          _streamSubscription.cancel();
          _listen();
        }
      }
    
      void _listen() {
        _streamSubscription = widget.stream.listen((value) {
          Navigator.pushNamed(context, '/someRoute/$value');
        });
      }
    
      @override
      void dispose() {
        _streamSubscription.cancel();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Container();
      }
    }
    

    Note that if you're using an InheritedWidget to obtain your stream (typically BLoC), you will want to use didChangeDependencies instead of initState/didUpdateWidget.

    This leads to:

    class Example extends StatefulWidget {
      @override
      ExampleState createState() => ExampleState();
    }
    
    class ExampleState extends State<Example> {
      StreamSubscription _streamSubscription;
      Stream _previousStream;
    
      void _listen(Stream<int> stream) {
        _streamSubscription = stream.listen((value) {
          Navigator.pushNamed(context, '/someRoute/$value');
        });
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        final bloc = MyBloc.of(context);
        if (bloc.stream != _previousStream) {
          _streamSubscription?.cancel();
          _previousStream = bloc.stream;
          _listen(bloc.stream);
        }
      }
    
      @override
      void dispose() {
        _streamSubscription.cancel();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Container();
      }
    }