BlocProvider.of() 使用不包含 Bloc 的上下文调用 - 即使它包含

Sta*_*ahp 26 navigation flutter bloc flutter-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(),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

这显然工作得很好。然后,如果用户成功登录,他将被导航到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(),
              ),
            ),
          );
        }
      },
[...]
Run Code Online (Sandbox Code Playgroud)

这也有效。当从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();
            }
        }
      },
[...]
Run Code Online (Sandbox Code Playgroud)

当我按下按钮导航到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
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?是因为我提取PopupMenuButton了不知何故丢失了块的小部件?我不明白我做错了什么。

Sta*_*ahp 19

我修好了它。内部App窗口小部件我创建LoginPage

home: BlocProvider<UserBloc>(
        create: (context) => UserBloc(UserRepository()),
        child: LoginPage(),
Run Code Online (Sandbox Code Playgroud)

LoginPage我只是将BlocBuilders一个包装成另一个

Widget build(BuildContext context) {
    return BlocListener<UserBloc, UserState>(
      listener: (context, state) {
        if (state is UserAuthenticated) {
          Navigator.of(context).push(
            MaterialPageRoute<HomePage>(
              builder: (_) => BlocProvider.value(
                value: BlocProvider.of<UserBloc>(context),
                child: BlocProvider<NewRelicBloc>(
                  create: (_) => NewRelicBloc(NewRelicRepository()),
                  child: HomePage(),
                ),
              ),
            ),
          );
        }
      },
[...]
Run Code Online (Sandbox Code Playgroud)

PopupMenuButton可前往[用户TokenPage

              Navigator.of(context).push(
                MaterialPageRoute<UserTokensPage>(
                  builder: (_) => BlocProvider.value(
                    value: BlocProvider.of<UserBloc>(context),
                    child: UserTokensPage(),
                  ),
                ),
              );
Run Code Online (Sandbox Code Playgroud)

这解决了我所有的问题。


小智 17

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

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

          ],
          child: App()
      )
  );
}
Run Code Online (Sandbox Code Playgroud)

你可以在你的应用程序的任何地方访问这个块

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


Moh*_*far 11

更改构建器中上下文的名称,无论是在bottomSheet还是materialPageRoute中。

因此,该块可以通过上下文访问父上下文,除非它要从构建器(底部表格)获取上下文。这可能会导致您无法访问 bloc 实例的错误。

showModalBottomSheet(
     context: context,
     builder: (context2) {  ===> change here to context2
     BlocProvider.value(
          value: BlocProvider.of<BlocA>(context),
          child: widgetA(),
                       ),
     }
Run Code Online (Sandbox Code Playgroud)


om-*_*-ha 6

解决方案

方法A:UserBloc直接访问provider实例,不传递

我更喜欢这个解决方案,因为它需要更少的代码。

A.1CustomPopupButton使用提供者Consumer包装实例,以便在 UserBloc 通知侦听器值更改时重建自身。

改变这个:

actions: <Widget>[
    CustomPopupButton(),
],
Run Code Online (Sandbox Code Playgroud)

到:

actions: <Widget>[
    Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
      return CustomPopupButton(),
    });
],
Run Code Online (Sandbox Code Playgroud)

A.2 在无状态小部件内更改提供者实例调用以禁用监听值更改——“监听”和由此产生的“重建”已经由Consumer.

A.2.1改变这一点:

value: BlocProvider.of<UserBloc>(context),
Run Code Online (Sandbox Code Playgroud)

到:

value: BlocProvider.of<UserBloc>(context, listen: false),
Run Code Online (Sandbox Code Playgroud)

A.2.2并更改此:

BlocProvider.of<UserBloc>(context).add(SignOut());
Run Code Online (Sandbox Code Playgroud)

到:

BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
Run Code Online (Sandbox Code Playgroud)

方法B:传递UserBloc提供者实例

与方法 A 相同,但:

  • 在A.1您想通过userBloc这样的:return CustomPopupButton(userBloc: userBloc),
  • 你会final UserBloc userBloc;在里面声明成员属性CustomPopupButton
  • 在 A.2 中,你会这样做:userBloc.add(SignOut());而不是BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

解释

flutter_bloc正在使用Provider,要了解发生了什么,最好了解 Provider。请参考我在此处的回答以了解我对您问题的回答,并listen更好地了解 Provider 和flag。