31

首先,我确实知道 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了以某种方式丢失块的小部件吗?我不明白我做错了什么。

4

6 回答 6

25

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

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

          ],
          child: App()
      )
  );
}

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

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

于 2020-02-20T09:30:48.073 回答
20

我修好了它。在App我创建LoginPage的小部件内部

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

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

PopupMenuButton用户导航到TokenPage

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

这解决了我所有的问题。

于 2020-01-29T15:27:40.923 回答
6

解决方案

方法A:UserBloc直接访问提供者实例而不传递它

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

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

改变这个:

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

到:

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

A.2 无状态小部件内的更改提供程序实例调用以禁用侦听值更改——“侦听”和由此产生的“重建”已经由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),.
  • 您将final UserBloc userBloc;在内部声明成员属性CustomPopupButton
  • 在 A.2 中你会这样做:userBloc.add(SignOut());而不是BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

解释

flutter_bloc正在使用Provider,要知道发生了什么,最好了解 Provider。请参考我这里的回答,了解我对您问题的回答,更好地了解 Provider 和listenflag。

于 2020-01-25T11:36:05.777 回答
1

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

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

showModalBottomSheet(
     context: context,
     builder: (context2) {  ===> change here to context2
     BlocProvider.value(
          value: BlocProvider.of<BlocA>(context),
          child: widgetA(),
                       ),
     }
于 2021-03-25T04:57:21.770 回答
1

您需要将小部件分解为两个小部件(出于可测试性原因,我建议这样做)或使用 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

于 2021-05-28T12:18:40.020 回答
0

您不必使用 BlocProvider.value() 导航到另一个屏幕,您可以将 MaterialApp 作为其子项包装到 BlocProvider 中

于 2020-02-18T21:10:22.710 回答