Flutter:从 StreamBuilder 构建器回调内部导航到另一个屏幕

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

我有一个闪屏和一个 StreamBuilder 发出的状态,其中包含有关身份验证状态的信息。当身份验证状态已知时,我想导航到登录页面或主页。但是当我写类似 Navigator.of(context).pushReplacement(...) 的东西时,我得到

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)

我发现的唯一解决方法是使用 Future(...).then(navigate) 将导航安排到事件队列的末尾,但这很糟糕。这里有足够的解决方案吗?

您可以在构建方法之外收听您的流并从那里重定向到另一个视图。

---- 已编辑----

这是一个如何做到这一点的例子:

@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");
        }
    });
}

记得保存从listen方法返回的StreamSubscription对象,这样你就可以在dispose()上取消订阅了。

我仍然对我的解决方案不满意,但我使用了

if(snapshot.hasdata && snapshot.data.navigate) {
     Future.microtask(() => Navigator.of(context).push... );
   }