Why can I not retrieve a BLoC from another Page/Route?

Question

I am trying to retrieve the reference of a BLoC, a Model… via BlocProvider.of(context) or ScopedModel.of(context) and it returns null. Why is this?

Answer

Most of the time, this comes from the fact that you switched from one Page (= Route) to another, using the Navigator.

Typical use case:

class HomePage extends StatefulWidget {
    @override
    _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

    MyBloc bloc;
    
    @override
    void initState(){
        super.initState();
        bloc = MyBloc();
    }

    @override
    void dispose(){
        bloc?.dispose();
        super.dispose();
    }
    @override
    Widget build(BuildContext context){
        return BlocProvider<MyBloc>(
            bloc: bloc,
            child: Scaffold(
                appBar: AppBar(
                    title: Text('Home Page'),
                    actions: <Widget>[
                        IconButton(
                            icon: Icon(Icons.search),
                            onPress:(){
                                Navigator.of(context).pushNamed('/searchPage');
                            }
                        ),
                    ]
                ),
                body: Container(),
            ),
        );
    }
}

class SearchPage extends StatelessWidget {
    @override
    Widget build(BuildContext context){
        MyBloc bloc = BlocProvider.of<MyBloc>(context);

        return Container();
    }
}

So, when you click the Search icon on the HomePage, you are redirected to the SearchPage and the line: “MyBloc bloc = BlocProvider.of(context)” returns null…

Why is this?

In order to understand, you need to keep in mind how Flutter handles the Pages (= Route)…

Routes = Stack of OverlayEntry()

When you are instantiating a MaterialApp, in fact, Flutter creates a series of Widgets you usually do not need to worry about and one of them is an Overlay.

This Overlay has a Stack as a descendant. This Stack will be used to “stack” the Pages (something like to put them one on top of the other).

The following schema shows a very simplified view of the hierarchy, which results from the code here above:

Simplified Hierarchical view Simplified Hierarchical view

The MOST important here is to remember that both stacked Pages DOT NOT SHARE ANYTHING. The only thing they have a common is the same ancestors: the Stack and upward.

In other words, Widgets which are descendant of one page ARE NOT VISIBLE FROM ANOTHER PAGE (without doing any trick…).

When, from the SearchPage, you are using BlocProvider.of(context), the system looks for a Widget of a certain type in the list of all its ANCESTORS (= ancestors of the SearchPage Widget).

In our case (see simplified schema), it will never find the requested Widget as the latter is not in its ancestors’ list.


How to solve this?

There are several possibilities, let’s see 3 of them:

1. Put on “Top of Everything”

If the BLoC is global and unique for the whole application, nothing prevents from injecting it as an ancestor of the MaterialApp, like this:

    void main(){
        runApp(Application());
    }

    class Application extends StatelessWidget {
        @override
        Widget build(BuildContext context){
            return BlocProvider<MyBloc>(
                bloc: MyBloc(),
                child: MaterialApp(
                    home: HomePage(),
                ),
            );
        }
    }

This way, the BLoC will be available from EVERYWHERE in the application since it will be an ancestor of almost all widgets.

2. Make it a “Singleton”

If the BLoC is global and unique for the whole application, nothing prevents from using a “Singleton”, as follows:

class MyBloc implements BlocBase {

    @override
    void dispose(){
        //...
    }

    // -------  SINGLETON ------
    static final MyBloc _instance = MyBloc._internal();
    factory MyBloc() => _instance;
    MyBloc._internal();
}
MyBloc myBloc = MyBloc();

To use the BLoC, it becomes very easy: simply make a reference to the “myBloc” global variable.

3. Pass it as an argument

Another possibility is to pass the instance of the BLoC in argument to another page.

I personally do not favor this solution as it creates a tight coupling but sometimes, it is very difficult to avoid it.


Conclusions

This problem is very common and faced by every new Flutter developer, one day or another.

I really hope it is now a bit clearer why this happens.