Search code examples
flutterstream-builder

Flutter: navigate to another screen from inside of StreamBuilder builder callback


I have a splash screen and a StreamBuilder that emits a state that contains information about authentication status. When the authentication status is known, I want to navigate either to sign in page or home page. But when I write something like Navigator.of(context).pushReplacement(...) I get

I/flutter ( 2058): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ I/flutter ( 2058): The following assertion was thrown building StreamBuilder(dirty, state: I/flutter ( 2058): _StreamBuilderBaseState>#f4346): I/flutter ( 2058): setState() or markNeedsBuild() called during build. I/flutter ( 2058): This Overlay widget cannot be marked as needing to build because the framework is already in the I/flutter ( 2058): process of building widgets. A widget can be marked as needing to be built during the build phase I/flutter ( 2058): only if one of its ancestors is currently building. This exception is allowed because the framework I/flutter ( 2058): builds parent widgets before children, which means a dirty descendant will always be built. I/flutter ( 2058): Otherwise, the framework might not visit this widget during this build phase. I/flutter ( 2058): The widget on which setState() or markNeedsBuild() was called was: I/flutter ( 2058): Overlay-[LabeledGlobalKey#e0460](state: OverlayState#ab1a5(entries: I/flutter ( 2058): [OverlayEntry#4e962(opaque: false; maintainState: false), OverlayEntry#7656a(opaque: false; I/flutter ( 2058): maintainState: true), OverlayEntry#1f86e(opaque: false; maintainState: false), I/flutter ( 2058): OverlayEntry#05a15(opaque: false; maintainState: true)])) I/flutter ( 2058): The widget which was currently being built when the offending call was made was: I/flutter ( 2058): StreamBuilder(dirty, state: _StreamBuilderBaseState>#f4346) I/flutter ( 2058): I/flutter ( 2058): When the exception was thrown, this was the stack: I/flutter ( 2058): #0 Element.markNeedsBuild. (package:flutter/src/widgets/framework.dart:3503:11) I/flutter ( 2058): #1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3529:6) I/flutter ( 2058): #2 State.setState (package:flutter/src/widgets/framework.dart:1133:14) I/flutter ( 2058): #3 OverlayState.insertAll (package:flutter/src/widgets/overlay.dart:346:5) I/flutter ( 2058): #4 OverlayRoute.install (package:flutter/src/widgets/routes.dart:43:24) I/flutter ( 2058): #5 TransitionRoute.install (package:flutter/src/widgets/routes.dart:180:11) I/flutter ( 2058): #6 ModalRoute.install (package:flutter/src/widgets/routes.dart:895:11) I/flutter ( 2058): #7 NavigatorState.pushReplacement (package:flutter/src/widgets/navigator.dart:1799:14) I/flutter ( 2058): #8 _replace (package:map_chat/application/navigation.dart:75:27) I/flutter ( 2058): #9 _SignInPage.replace (package:map_chat/application/navigation.dart:67:5) I/flutter ( 2058): #10 Roadmap.replace (package:map_chat/application/navigation.dart:25:18) I/flutter ( 2058): #11 _SplashPageState._buildPageBasedOnAuthenticationState (package:map_chat/feature/splash.dart:52:19) I/flutter ( 2058): #12 _SplashPageState._buildSplashScreen (package:map_chat/feature/splash.dart:40:11) I/flutter ( 2058): #13 _SplashPageState._buildPage. (package:map_chat/feature/splash.dart:27:18) I/flutter ( 2058): #14 StreamBuilder.build (package:flutter/src/widgets/async.dart:425:74) I/flutter ( 2058): #15 _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:125:48) I/flutter ( 2058): #16 StatefulElement.build (package:flutter/src/widgets/framework.dart:3825:27) I/flutter ( 2058): #17 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3739:15) I/flutter ( 2058): #18 Element.rebuild (package:flutter/src/widgets/framework.dart:3565:5) I/flutter ( 2058): #19 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2278:33) I/flutter ( 2058): #20 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:700:20) I/flutter ( 2058): #21 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:286:5) I/flutter ( 2058): #22 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1012:15) I/flutter ( 2058): #23 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:952:9) I/flutter ( 2058): #24 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.scheduleWarmUpFrame. (package:flutter/src/scheduler/binding.dart:773:7) I/flutter ( 2058): #33 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:382:19) I/flutter ( 2058): #34 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:416:5) I/flutter ( 2058): #35 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:171:12) I/flutter ( 2058): (elided 8 frames from package dart:async and package dart:async-patch)

The only workaround I found is to schedule the navigation to the end of event queue using Future(...).then(navigate) but that's sick. Is here an adequate solution for this?


Solution

  • You can listen your stream outside of build method and redirect to another view from there.

    ---- EDITED ----

    This is an example of how you can do that:

    @override
    void initState() {
        super.initState();
    
        Future.delayed(Duration.zero, _verify);
    }
    
    void _verify() {
        final _myBloc = BlocProvider.getBloc<MyBloc>();
    
        _myBloc.myStream.listen((data) {
            // Redirect to another view, given your condition
            if (data) { 
                Navigator.of(context).pushNamed("my-new-route");
            }
        });
    }
    

    Just remember to save the StreamSubscription object returned from listen method, so you can cancel the subscription on dispose().