Navigating to a new screen when stream value in BLOC changes
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(); }}
You can extend StreamBuilder
with custom listener like this:
typedef StreamListener<T> = void Function(T value);class StreamListenableBuilder<T> extends StreamBuilder<T> { final StreamListener<T> listener; const StreamListenableBuilder({ Key key, T initialData, Stream<T> stream, @required this.listener, @required AsyncWidgetBuilder<T> builder, }) : super(key: key, initialData: initialData, stream: stream, builder: builder); @override AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) { listener(data); return super.afterData(current, data); }}
Then connect listener for navigation this way:
StreamListenableBuilder( stream: bloc.streamValue, listener: (value) { if (value==1) { Navigator.push( context, MaterialPageRoute(builder: (context) => SomeNewScreen()), ); } }, builder: (BuildContext context, AsyncSnapshot<int> snapshot) { return Container(); });