Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BlocProvider.of() called with a context that does not contain a Bloc - even that it does

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.

like image 544
Stahp Avatar asked Jan 24 '20 19:01

Stahp


People also ask

What is BlocProvider in Flutter?

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.

What is the difference between BLoC and Flutter BLoC?

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.


2 Answers

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

like image 68
Amesh Fernando Avatar answered Sep 20 '22 08:09

Amesh Fernando


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.

like image 29
Stahp Avatar answered Sep 22 '22 08:09

Stahp