导航到新页面时 BLOC 丢失上下文

Posted

技术标签:

【中文标题】导航到新页面时 BLOC 丢失上下文【英文标题】:BLOC losing context when navigating to a new page 【发布时间】:2020-12-13 01:26:35 【问题描述】:

我正在使用 BLOC 模式在我的应用中对用户进行身份验证。我有一个主要的 BlocProvider 来包装我的应用程序。和一个 BlocBuilder 根据认证状态构建。

如果用户未通过身份验证,我会使用入门/介绍屏幕导航到登录屏幕。

登录屏幕被包裹在另一个 BlocProvider 中,该 BlocProvider 包含一个用于登录的按钮,并在登录成功时添加一个登录事件。

问题是当我从入职屏幕导航时,我失去了主要的 authenticationBloc 上下文。推送新屏幕后,我需要做什么才能访问身份验证块。

void main() 
  WidgetsFlutterBinding.ensureInitialized();
  Bloc.observer = SimpleBlocObserver();

  runApp(
    MyApp(),
  );


class AuthenticationWrapper extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: BlocProvider<AuthenticationBloc>(
        create: (context) => AuthenticationBloc()..add(AppStarted()),
        child: MyApp(),
      ),
    );
  

class MyApp extends StatelessWidget 

  @override
  Widget build(BuildContext context) 
    return BlocListener<AuthenticationBloc, AuthenticationState>(
      listener: (context, state) 
        if (state is Authenticated) 
          _appUserProfileRepository = AppUserProfileRepository();
        
      ,
      child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
        builder: (context, state) 

          _authCredentialHelper = state.authCredentialHelper;

          if (state is Uninitialized) 
            return SplashScreen();
          

          if (state is Unauthenticated) 
            return OnboardingScreens(authCredentialHelper: _authCredentialHelper);
          

          if (state is InvalidRegistration) 
            return RegisterProfileScreen(authCredentialHelper: _authCredentialHelper);
          

          if (state is Authenticated) 
              xxx
          

          return Scaffold(body: Center(child: LoadingIndicator()));
        ,
      ),
    );
  

这是入职屏幕,我一导航就松开了 authenticationbloc 上下文

class OnboardingScreens extends StatelessWidget 
  final AuthCredentialHelper authCredentialHelper;

  OnboardingScreens(this.authCredentialHelper);

  _pages(BuildContext context) 
    return [
      xxx
    ];
  

  _getStartedClicked(BuildContext context) 
    Navigator.push(context, MaterialPageRoute(builder: (context) 
      return LoginScreen(authCredentialHelper: authCredentialHelper);
    ));
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: SafeArea(
        child: IntroductionScreen(
          pages: _pages(context),
          onDone: () => _getStartedClicked(context),
          showSkipButton: true,
          done: xxx
        ),
      ),
    );
  


在 1 处添加断点时,上下文可以正常使用 BlocProvider.of(context) 的有效值

步到 2. 给我一个错误: 使用不包含 AuthenticationBloc 类型的 Cubit 的上下文调用 BlocProvider.of()。

  _getStartedClicked(BuildContext context) 
    1----->Navigator.push(context, MaterialPageRoute(builder: (context) 
    2----->return LoginScreen(authCredentialHelper: authCredentialHelper);
    ));
  

这是登录屏幕代码

class LoginScreen extends StatelessWidget 
  final AuthCredentialHelper authCredentialHelper;

  LoginScreen(this.authCredentialHelper);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.arrow_back, color: darkBlue),
          onPressed: () => Navigator.of(context).pop(),
        ),
        backgroundColor: Colors.transparent,
        elevation: 0.0,
      ),
      body: SafeArea(
        child: Center(
          child: BlocProvider<LoginBloc>(
            create: (context) => LoginBloc(authCredentialHelper: authCredentialHelper),
            child: LoginForm(authCredentialHelper: authCredentialHelper),
          ),
        ),
      ),
    );
  

收到此错误:

The following assertion was thrown building _InheritedProviderScope<LoginBloc>(value: Instance of 'LoginBloc'):
BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.

No ancestor could be found starting from the context that was passed to BlocProvider.of<AuthenticationBloc>().

This can happen if the context you used comes from a widget above the BlocProvider.

【问题讨论】:

您可以手动将 authbloc 实例传递给新路由并使用另一个 BlocProvider 重新提供它,或者只是不理会 Navigator 路由并让另一个 Bloc 层来处理显示的页面。 这是因为当你调用 navigator.push(context, ... (context) ...) 时,第二个上下文和第一个不同。 【参考方案1】:

改变这个:

   Navigator.push(context, MaterialPageRoute(builder: (context) 
      return LoginScreen(authCredentialHelper: authCredentialHelper);
   ));

   Navigator.push(
     context,
     MaterialPageRoute(builder: (contextLoginScreen) 
        return BlocProvider.value(
            value: context.bloc<AuthenticationBloc>(),
            child: LoginScreen(authCredentialHelper: authCredentialHelper));
     ),
   );

【讨论】:

附言。直接从身份验证屏幕进入登录屏幕可以正常工作。 IE。添加 return LoginScreen(authCredentialHelper: authCredentialHelper);当状态未验证时 我将身份验证块上移了一级并修改了原始问题。我仍然收到错误消息。在构建 _InheritedProviderScope(值:“LoginBloc”实例)时引发了以下断言:使用不包含 AuthenticationBloc 类型的 Cubit 的上下文调用 BlocProvider.of()。从传递给 BlocProvider.of(). 的上下文开始找不到祖先

以上是关于导航到新页面时 BLOC 丢失上下文的主要内容,如果未能解决你的问题,请参考以下文章

导航到新页面时防止颤动块状态更改

使用 BLoC 的 Flutter 导航:用于从 Navigator 推送路由的上下文必须是 Navigator 小部件的后代

如何在上下文之间共享集团

使用不包含 Bloc 类型的上下文调用 Flutter BlocProvider.of()

Flutter - 导航到新屏幕并返回

由于上下文问题,无法从子类访问 bloc?