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

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.


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


Solution

Method A: Access UserBloc provider instance directly without passing it

I prefer this solution since it requires less code.

A.1 Wrap CustomPopupButton instance with provider Consumer so it rebuilds itself whenever UserBloc notifies listeners of value changes.

Change this:

actions: <Widget>[
    CustomPopupButton(),
],

To:

actions: <Widget>[
    Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
      return CustomPopupButton(),
    });
],

A.2 Change Provider instance invocation inside the stateless widget to disable listening to value changes -- "listening" and resulting "rebuilds" are already done by Consumer.

A.2.1 Change this:

value: BlocProvider.of<UserBloc>(context),

To:

value: BlocProvider.of<UserBloc>(context, listen: false),

A.2.2 And change this:

BlocProvider.of<UserBloc>(context).add(SignOut());

To:

BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

Method B: pass UserBloc provider instance

Same thing as Method A, but:

  • In A.1 you'd pass userBloc like this: return CustomPopupButton(userBloc: userBloc),.
  • You'd declare final UserBloc userBloc; member property inside CustomPopupButton.
  • In A.2 you'd do this: userBloc.add(SignOut()); instead of BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

Explanation

flutter_bloc is using Provider, to be aware what's going on it's better understand Provider. Please refer to my answer here to understand my answer to your question, and to understand Provider and listen flag better.