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

dy2hfwbg  于 2022-12-19  发布在  Flutter
关注(0)|答案(6)|浏览(173)

首先,我知道BLoC应该如何工作,它背后的思想,我知道BlocProvider()BlocProvider.value()构造函数之间的区别。
为简单起见,我的应用程序有3个页面,其中包含一个小部件树,如下所示:
第一个月第二个月第一个月=〉第一个月第三个月=〉第一个月第四个月=〉第一个月第五个月
我希望我的LoginPage()能够访问UserBloc,因为我需要登录用户等。为此,我在App()小部件处 Package LoginPage()构建器,如下所示:

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(),
      ),
    );
  }
}

显然,这样做效果很好。然后,如果 User 成功登录,他将被导航到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(),
              ),
            ),
          );
        }
      },
[...]

这也是可行的。当从HomePageuser 导航到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小工具,不知何故丢失了块吗?我不明白我能做错什么。

cwtwac6a

cwtwac6a1#

您可以将需要通过应用程序访问的块 Package 在应用程序的入口点,如下所示

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

          ],
          child: App()
      )
  );
}

您可以通过以下方式在应用的任何位置访问此区块
第一个月

2guxujil

2guxujil2#

2022年3月10日编辑

由于这个线程变得非常流行,我觉得我需要添加一些评论。
如果您的目标是使用不是MaterialApp小部件上方提供的块,而是通过用BlocProvider Package 小部件(例如,某个页面)使该小部件能够访问该块来声明小部件树中的某个位置,则这是一个有效的解决方案。
MultiBlocProvider中声明所有的区块会更容易避免问题(就像我之前说的),但创建这个主题时并没有考虑到这一点。请随意投赞成票,并使用 Amesh Fernando 回复中描述的方法,但要知道两者的区别。
我修复了它。在App小部件中,我使用

home: BlocProvider<UserBloc>(
        create: (context) => UserBloc(UserRepository()),
        child: LoginPage(),

LoginPage中,我简单地将BlocBuilders一个 Package 到另一个中

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(),
                ),
              ),
            ),
          );
        }
      },
[...]

PopupMenuButton将 * 用户 * 导航到TokenPage,使用

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

我的问题都解决了。

gcmastyq

gcmastyq3#

溶液

方法A:直接访问UserBloc提供程序示例而不传递它

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

A.1使用提供程序Consumer Package CustomPopupButton示例,以便每当UserBloc通知侦听器值发生更改时,该示例都会重建自身。

更改此内容:

actions: <Widget>[
    CustomPopupButton(),
],

收件人:

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

A.2更改无状态小部件内的Provider示例调用,以禁用侦听值更改--Consumer已经完成了"侦听"和相应的"重建"。

    • A.2.1**更改:
value: BlocProvider.of<UserBloc>(context),

收件人:

value: BlocProvider.of<UserBloc>(context, listen: false),
    • A.2.2**并将其更改为:
BlocProvider.of<UserBloc>(context).add(SignOut());

收件人:

BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

方法B:传递UserBloc提供程序示例

与方法A相同,但:

  • 在A.1中,您将像这样传递userBlocreturn CustomPopupButton(userBloc: userBloc),.
  • 您可以在CustomPopupButton中声明final UserBloc userBloc;成员属性。
  • 在A.2中,您将执行以下操作:userBloc.add(SignOut());而不是BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

解释

flutter_bloc正在使用Provider,要了解发生了什么,最好了解提供程序请参阅我对here回答,以了解我对您问题回答,并更好地了解提供程序和listen标志

cpjpxq1n

cpjpxq1n4#

在bottomSheet或materialPageRoute中更改生成器中的上下文名称。

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

showModalBottomSheet(
     context: context,
     builder: (context2) {  ===> change here to context2
     BlocProvider.value(
          value: BlocProvider.of<BlocA>(context),
          child: widgetA(),
                       ),
     }
ktecyv1j

ktecyv1j5#

您需要将小部件分解为两个小部件(出于可测试性原因,我建议这样做),或者使用Builder小部件获取子上下文。

class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => TestCubit(), child: MyHomeView(), ); } } class MyHomeView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: RaisedButton(onPressed: () => BlocProvider.of<TestCubit>(context)...) ), ); } }

来源:由Felix Angelov解决,https://github.com/felangel/bloc/issues/2064

xu3bshqb

xu3bshqb6#

您不必使用BlocProvider.value()来导航到另一个屏幕,您只需将MaterialApp Package 到BlocProvider中作为它的子项即可

相关问题