web-dev-qa-db-ja.com

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

それは明らかにうまくいきます。次に、serが正常にログインすると、HomePageに移動します。ここで、HomePageで2つの異なるブロックにアクセスする必要があるため、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(),
              ),
            ),
          );
        }
      },
[...]

これも機能します。 HomePageserからUserTokensPageに移動すると問題が発生します。 UserTokensPageで、BlocProvider.value()コンストラクターで渡したい既存のUserBlocが必要です。私はそれをこのようにします:

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ウィジェットを抽出したからでしょうか?何がいけないのかわかりません。

5
Stahp

それを私が直した。 Appウィジェット内でLoginPageを作成します

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

LoginPageBlocBuildersをもう1つにラップするだけです

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

そして、それは私のすべての問題を解決しました。

2
Stahp

このようにアプリのエントリポイントでラップすることにより、アプリ全体でアクセスする必要があるブロックをラップすることができます

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

          ],
          child: App()
      )
  );
}
_

アプリのどこからでもこのブロックにアクセスできます

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

0
Amesh Fernando

別の画面に移動するためにBlocProvider.value()を使用する必要はありません。MaterialAppをその子としてBlocProviderにラップするだけです。

0
Tarek Mahfouz