まず、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(),
),
),
);
}
},
[...]
これも機能します。 HomePage
serから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
ウィジェットを抽出したからでしょうか?何がいけないのかわかりません。
それを私が直した。 App
ウィジェット内でLoginPage
を作成します
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
LoginPage
でBlocBuilders
をもう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(),
),
),
);
そして、それは私のすべての問題を解決しました。
このようにアプリのエントリポイントでラップすることにより、アプリ全体でアクセスする必要があるブロックをラップすることができます
_ runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}
_
アプリのどこからでもこのブロックにアクセスできます
BlocProvider.of<UserBloc>(context).add(event of user bloc());
別の画面に移動するためにBlocProvider.value()を使用する必要はありません。MaterialAppをその子としてBlocProviderにラップするだけです。