首页 > 解决方案 > BlocProvider.of() 使用不包含 Bloc 的上下文调用 - 即使它包含

问题描述

首先,我确实知道 BLoC 应该如何工作,它背后的想法,我知道构造函数BlocProvider()BlocProvider.value()构造函数之间的区别。

为简单起见,我的应用程序有 3 个页面,其中的小部件树如下所示:

App()=> LoginPage()=> HomePage()=>UserTokensPage()

我希望我LoginPage()能够访问,UserBloc因为我需要登录用户等。为此,我将LoginPage()构建器包装在App()小部件上,如下所示:

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: BlocProvider<UserBloc>(
        create: (context) => UserBloc(UserRepository()),
        child: LoginPage(),
      ),
    );
  }
}

这显然工作得很好。然后,如果用户成功登录,他将被导航到HomePage。现在,我需要访问我的两个不同的块,HomePage所以我用来进一步MultiBlocProvider传递现有的UserBloc并创建一个名为DataBloc. 我这样做:

  @override
  Widget build(BuildContext context) {
    return BlocListener<UserBloc, UserState>(
      listener: (context, state) {
        if (state is UserAuthenticated) {
          Navigator.of(context).push(
            MaterialPageRoute<HomePage>(
              builder: (_) => MultiBlocProvider(
                providers: [
                  BlocProvider.value(
                    value: BlocProvider.of<UserBloc>(context),
                  ),
                  BlocProvider<DataBloc>(
                    create: (_) => DataBloc(DataRepository()),
                  ),
                ],
                child: HomePage(),
              ),
            ),
          );
        }
      },
[...]

这也有效。当从HomePage 用户导航到UserTokensPage. 在UserTokensPage我需要我已经存在UserBloc的我想通过BlocProvider.value()构造函数传递。我这样做:

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: false,
        title: Text('My App'),
        actions: <Widget>[
          CustomPopupButton(),
        ],
      ),

[...]

class CustomPopupButton extends StatelessWidget {
  const CustomPopupButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<String>(
      icon: Icon(Icons.more_horiz),
      onSelected: (String choice) {
        switch (choice) {
          case PopupState.myTokens:
            {
              Navigator.of(context).push(
                MaterialPageRoute<UserTokensPage>(
                  builder: (_) => BlocProvider.value(
                    value: BlocProvider.of<UserBloc>(context),
                    child: UserTokensPage(),
                  ),
                ),
              );
            }
            break;
          case PopupState.signOut:
            {
              BlocProvider.of<UserBloc>(context).add(SignOut());
              Navigator.of(context).pop();
            }
        }
      },
[...]

当我按下按钮导航到MyTokensPage我收到错误消息:

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building Builder(dirty):
        BlocProvider.of() called with a context that does not contain a Bloc of type UserBloc.

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

        This can happen if:
        1. The context you used comes from a widget above the BlocProvider.
        2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.

        Good: BlocProvider<UserBloc>(create: (context) => UserBloc())
        Bad: BlocProvider(create: (context) => UserBloc()).

        The context used was: CustomPopupButton

我究竟做错了什么?是因为我提取PopupMenuButton了以某种方式丢失块的小部件吗?我不明白我做错了什么。

标签: flutternavigationblocflutter-bloc

解决方案


您可以像这样在应用程序的入口点包装您需要通过应用程序访问的 Bloc

  runApp(
      MultiBlocProvider(
          providers: [
            BlocProvider<UserBloc>(
              create: (context) =>
                  UserBloc(UserRepository()),
            ),

          ],
          child: App()
      )
  );
}

你可以在你的应用程序的任何地方通过

BlocProvider.of<UserBloc>(context).add(event of user bloc());


推荐阅读