简体   繁体   English

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

[英]BlocProvider.of() called with a context that does not contain a Bloc - even that it does

First of, I do know how BLoC suppose to work, the idea behind it and I know the difference between BlocProvider() and BlocProvider.value() constructors.首先,我知道 BLoC 应该如何工作,它背后的想法,我知道BlocProvider()BlocProvider.value()构造函数之间的区别。

For simplicity, my application has 3 pages with a widget tree like this:为简单起见,我的应用程序有 3 个页面,其中包含一个像这样的小部件树:

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

I want my LoginPage() to have access to UserBloc because i need to log in user etc. To do that, I wrap LoginPage() builder at App() widget like this:我希望我的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(),
      ),
    );
  }
}

That obviously works just fine.这显然工作得很好。 Then, if User logs in successfully, he is navigated to HomePage .然后,如果用户成功登录,他将被导航到HomePage Now, I need to have access to two different blocs at my HomePage so I use MultiBlocProvider to pass existing UserBloc further and create a brand new one named DataBloc .现在,我需要在我访问两个不同的集团HomePage ,所以我用MultiBlocProvider通过现有UserBloc进一步,创造一个全新的命名DataBloc I do it like this:我这样做:

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

This also works.这也有效。 Problem happens when from HomePage user navigates to UserTokensPage .当从HomePage用户导航到UserTokensPage时会发生问题。 At UserTokensPage I need my already existing UserBloc that I want to pass with BlocProvider.value() constructor.UserTokensPage我需要我已经存在的UserBloc ,我想用BlocProvider.value()构造函数传递它。 I do it like this:我这样做:

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

When I press button to navigate to MyTokensPage i get error with message:当我按下按钮导航到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

What am I doing wrong?我究竟做错了什么? Is it because i have extracted PopupMenuButton widget that somehow loses blocs?是不是因为我提取的PopupMenuButton小部件以某种方式丢失了块? I don't understand what I can be doing wrong.我不明白我做错了什么。

I fixed it.我修好了它。 Inside App widget i create LoginPage withApp小部件中,我创建了LoginPage

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

At LoginPage I simply wrap BlocBuilders one into anotherLoginPage我只是将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(),
                ),
              ),
            ),
          );
        }
      },
[...]

PopupMenuButton navigates User to TokenPage with PopupMenuButton用户导航到TokenPage

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

And that solved all my problems.这解决了我所有的问题。

You can just wrap the Blocs you need to access through out the app by wrapping it at the entry point of the app like this您可以像这样将其包装在应用程序的入口点,从而将您需要通过应用程序访问的 Blocs 包装起来

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

          ],
          child: App()
      )
  );
}

and you can access this bloc at anywhere of your app by你可以在你的应用程序的任何地方访问这个块

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

Solution解决方案

Method A: Access UserBloc provider instance directly without passing it方法 A:直接访问UserBloc提供者实例而不传递它

I prefer this solution since it requires less code.我更喜欢这个解决方案,因为它需要更少的代码。

A.1 Wrap CustomPopupButton instance with provider Consumer so it rebuilds itself whenever UserBloc notifies listeners of value changes. A.1 使用提供者Consumer包装CustomPopupButton实例,以便在 UserBloc 通知侦听器值更改时重建自身。

Change this:改变这个:

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

To:到:

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

A.2 Change Provider instance invocation inside the stateless widget to disable listening to value changes -- "listening" and resulting "rebuilds" are already done by Consumer . A.2 在无状态小部件内更改 Provider 实例调用以禁用侦听值更改——“侦听”和由此产生的“重建”已经由Consumer完成。

A.2.1 Change this: A.2.1改变这一点:

value: BlocProvider.of<UserBloc>(context),

To:到:

value: BlocProvider.of<UserBloc>(context, listen: false),

A.2.2 And change this: A.2.2并更改此:

BlocProvider.of<UserBloc>(context).add(SignOut());

To:到:

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

Method B: pass UserBloc provider instance方法 B:传递UserBloc提供程序实例

Same thing as Method A, but:与方法 A 相同,但:

  • In A.1 you'd pass userBloc like this: return CustomPopupButton(userBloc: userBloc), .在 A.1 中,您userBloc像这样传递userBlocreturn CustomPopupButton(userBloc: userBloc),
  • You'd declare final UserBloc userBloc;你会声明final UserBloc userBloc; member property inside CustomPopupButton . CustomPopupButton内的成员属性。
  • In A.2 you'd do this: userBloc.add(SignOut());在 A.2 中你会这样做: userBloc.add(SignOut()); instead of BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());而不是BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

Explanation解释

flutter_bloc is using Provider , to be aware what's going on it's better understand Provider. flutter_bloc正在使用Provider ,要知道发生了什么,最好了解 Provider 。 Please refer to my answer here to understand my answer to your question, and to understand Provider and listen flag better.请参考我在此处的回答以了解我对您问题的回答,并更好地了解 Provider 和listen flag。

You need to either decompose your widget into two widgets (which I recommend for testability reasons) or use a Builder widget to get a child context.您需要将您的小部件分解为两个小部件(出于可测试性的原因,我建议这样做)或使用 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)...) ), ); } }

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

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

Change name of context in builder whether in bottomSheet or materialPageRoute.更改构建器中上下文的名称,无论是在 bottomSheet 还是 materialPageRoute 中。

So that bloc can access parent context through context unless it's going to take context from builder (bottom sheet).这样 bloc 就可以通过上下文访问父上下文,除非它要从构建器(底部工作表)获取上下文。 This can lead to an error which you can't reach the instance of bloc .这可能会导致您无法访问 bloc 实例的错误。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Flutter 使用不包含 Bloc 类型的上下文调用的 BLoC BlocProvider.of() - Flutter BLoC BlocProvider.of() called with a context that does not contain a Bloc of type BlocProvider.of() 使用不包含 TrackingBloc 类型的 Bloc 的上下文调用 - BlocProvider.of() called with a context that does not contain a Bloc of type TrackingBloc 使用不包含 FicheMvtBloc 类型的 Bloc 的上下文调用 BlocProvider.of() - BlocProvider.of() called with a context that does not contain a Bloc of type FicheMvtBloc BlocProvider.of() 使用不包含 OfflineBloc 类型的 Bloc 的上下文调用 - BlocProvider.of() called with a context that does not contain a Bloc of type OfflineBloc BlocProvider.of() 调用的上下文不包含 WeatherBloc 类型的 Bloc/Cubit - BlocProvider.of() called with a context that does not contain a Bloc/Cubit of type WeatherBloc Flutter:使用不包含 Bloc 类型的上下文调用 blocprovider.of() - Flutter: blocprovider.of() called with a context that does not contain a Bloc of type 使用不包含 Bloc 类型的上下文调用 BlocProvider.of() - BlocProvider.of() called with a context that does not contain a Bloc of type BlocProvider.of() 使用不包含 CLASS 类型的 Bloc 的上下文调用 - BlocProvider.of() called with a context that does not contain a Bloc of type CLASS 使用不包含 Bloc 类型的上下文调用 Flutter BlocProvider.of() - Flutter BlocProvider.of() called with a context that does not contain a Bloc of type 使用不包含 TaharatBloc 类型的 Bloc 的上下文调用 BlocProvider.of() - BlocProvider.of() called with a context that does not contain a Bloc of type TaharatBloc
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM