Tout d'abord, je sais comment BLoC suppose de fonctionner, l'idée derrière cela et je connais la différence entre les constructeurs BlocProvider()
et BlocProvider.value()
.
Pour plus de simplicité, mon application dispose de 3 pages avec un arbre de widgets comme celui-ci:
App()
=> LoginPage()
=> HomePage()
=> UserTokensPage()
Je veux que ma LoginPage()
ait accès à UserBloc
parce que je dois me connecter, etc. Pour ce faire, j'enroule LoginPage()
builder à App()
widget comme celui-ci:
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(),
),
);
}
}
Cela fonctionne évidemment très bien. Ensuite, si ser se connecte avec succès, il accède à HomePage
. Maintenant, je dois avoir accès à deux blocs différents sur mon HomePage
donc j'utilise MultiBlocProvider
pour passer plus loin UserBloc
et en créer un nouveau nommé DataBloc
. Je le fais comme ça:
@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(),
),
),
);
}
},
[...]
Cela fonctionne également. Un problème se produit lorsque de HomePage
tilisateur accède à UserTokensPage
. A UserTokensPage
j'ai besoin de mon constructeur UserBloc
déjà existant que je veux passer avec le constructeur BlocProvider.value()
. Je le fais comme ça:
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();
}
}
},
[...]
Lorsque j'appuie sur le bouton pour accéder à MyTokensPage
, j'obtiens une erreur avec le message:
════════ 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
Qu'est-ce que je fais mal? Est-ce parce que j'ai extrait le widget PopupMenuButton
qui perd en quelque sorte des blocs? Je ne comprends pas ce que je peux faire de mal.
Je l'ai corrigé. À l'intérieur de App
widget que je crée LoginPage
avec
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
À LoginPage
j'enroule simplement BlocBuilders
l'un dans l'autre
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
navigue ser to TokenPage
avec
Navigator.of(context).Push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
Et cela a résolu tous mes problèmes.
UserBloc
sans la passerJe préfère cette solution car elle nécessite moins de code.
CustomPopupButton
avec le fournisseur Consommateur afin qu'elle se reconstruise chaque fois que UserBloc notifie aux auditeurs les changements de valeur.Change ça:
actions: <Widget>[
CustomPopupButton(),
],
À:
actions: <Widget>[
Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
return CustomPopupButton(),
});
],
Consumer
.A.2.1 Modifiez ceci:
value: BlocProvider.of<UserBloc>(context),
À:
value: BlocProvider.of<UserBloc>(context, listen: false),
A.2.2 Et changez ceci:
BlocProvider.of<UserBloc>(context).add(SignOut());
À:
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
UserBloc
Même chose que la méthode A, mais:
userBloc
comme ceci: return CustomPopupButton(userBloc: userBloc),
.final UserBloc userBloc;
Dans CustomPopupButton
.userBloc.add(SignOut());
au lieu de BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
flutter_bloc
Utilise Provider
, pour être conscient de ce qui se passe, il vaut mieux comprendre Provider. Veuillez vous référer à ma réponse ici pour comprendre ma réponse à votre question et pour mieux comprendre le fournisseur et le drapeau listen
.
Vous pouvez simplement envelopper les Blocs auxquels vous avez besoin d'accéder via l'application en les enveloppant au point d'entrée de l'application comme ceci
runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}
et vous pouvez accéder à ce bloc à n'importe quel endroit de votre application en
BlocProvider.of<UserBloc>(context).add(event of user bloc());
vous n'avez pas besoin d'utiliser BlocProvider.value () pour naviguer vers un autre écran, vous pouvez simplement envelopper MaterialApp dans BlocProvider en tant qu'enfant