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.
For simplicity, my application has 3 pages with a widget tree like this:
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:
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
. 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
. 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
. At UserTokensPage
I need my already existing UserBloc
that I want to pass with BlocProvider.value()
constructor. 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:
════════ 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? I don't understand what I can be doing wrong.
BlocProvider is a flutter widget that creates and provides a Bloc to all of its children. This is known as a dependency injection widget, so that a single instance of Bloc can be provided to multiple widgets within a subtree.
BLoC is an abbreviation for “Business logic component” which — like most state managers — aims to decouple business logic from the views. The Flutter bloc package provides you with all the tools to implement the BLoC pattern into your app.
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
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());
EDIT 10/03/2022
Since this thread became very popular I feel I need to add some comments.
This is valid solution if your goal is to use blocs that are not provided above your MaterialApp
widget, but instead being declared somewhere down the widget tree by wrapping your widget (eg. some page) with BlocProvider
making it possible for that widget to access the bloc.
It is easier to avoid problems by declaring all your blocs in MultiBlocProvider
somewhere up the widget tree (like I said before), but this topic was not created with that in mind. Feel free to upvote and use this aproach described in Amesh Fernando response but do that knowing the difference.
I fixed it. Inside App
widget i create LoginPage
with
home: BlocProvider<UserBloc>( create: (context) => UserBloc(UserRepository()), child: LoginPage(),
At LoginPage
I simply wrap BlocBuilders
one into another
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
Navigator.of(context).push( MaterialPageRoute<UserTokensPage>( builder: (_) => BlocProvider.value( value: BlocProvider.of<UserBloc>(context), child: UserTokensPage(), ), ), );
And that solved all my problems.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With