InheritedWidget - The getter was called on null after navigator.push
Asked Answered
C

2

11

I'm having trouble trying to access an InheritedWidget after navigating to a new widget.

I have my top level widget like this

class App extends StatelessWidget{
  build(context){
    return MaterialApp(
        title: 'Iniciar Sesion',
        home: LoginBlocProvider(child: WelcomeScreen()),
    );
  }  
}

Then WelcomeScreen has a button to navigate to LoginScreen

class WelcomeScreen extends StatelessWidget {

  @override Widget build(BuildContext context){
    return Scaffold(
      body: Center(child: MyButton)
    );
  }
}

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      shape: StadiumBorder(),
      child: Text('Ingresar', style: TextStyle(color: Colors.black)),
      elevation: 5.0,
      onPressed: () { 
        Navigator.of(context).push(MaterialPageRoute(
           builder: (BuildContext context) =>LoginScreen()
        ));
      }
    );
  }
}

Finally in LoginScreen I want to access the InheritedWidget

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  LoginBloc bloc;  

  @override void didChangeDependencies() {
    bloc = LoginBlocProvider.of(context);
    super.didChangeDependencies();
  }

  @override Widget build(BuildContext context){
    return Scaffold(
      body:
      Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Positioned(
            top: 0.0,
            child: Image.asset('assets/images/img.jpg',
              fit: BoxFit.none,
            ),
          ),
          _buildLogin(),
        ],
      ),
    );
  }
}

Edited: Here it's the LoginBlocProvider

class LoginBlocProvider extends InheritedWidget {
  final bloc;

  LoginBlocProvider({Key key, Widget child}) 
  : bloc = LoginBloc(), 
  super(key:key, child:child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static LoginBloc of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(LoginBlocProvider) as LoginBlocProvider).bloc;
  }
}

But, when I run the .of method of the InheritedWidget I get this error

I/flutter (27725): The following NoSuchMethodError was thrown building Builder:
I/flutter (27725): The getter 'bloc' was called on null.
I/flutter (27725): Receiver: null
I/flutter (27725): Tried calling: bloc

I have the impression that it all has to do with the context in the Navigator.push builder method. Because if I use the LoginScreen widget without the Navigator.push, I can use the InheritedWidget perfectly fine

The error is happening because the context passed to the LoginBlocProvider.of() method is not finding the instance.

Any thoughts on this?

Carbuncle answered 10/9, 2018 at 17:53 Comment(5)
Hi @Sebastian. 1. Your code does not show the LoginBlocProvider (could you please share). 2. I don't understand why you get the bloc in the didChangeDependencies. I would rather a) get it from the build method, before the return Scaffold or, b) Add an void initState(){super.initState();WidgetsBinding.instance.addPostFrameCallback((_){bloc = LoginBlocProvider.of(context);}); Could you please try and/or answer? Cheers.Preindicate
@Preindicate I updated the question with the LoginBlocProvider. I try to get the bloc in the didChangeDependencies method because after that I will set up a stream listener. I didn't do it in initState() because I don't have access to the context.Carbuncle
@Preindicate One more thing. This issue goes beyond where I place the call to the inherited widget. For example if I place it in the build function I get the same getter called on null error. That's what's throwing me off. And that's why I thing it had something to do with the navigator.push methodCarbuncle
@Carbuncle you have access to context anywhere within a State class, so you can use it in initState. See here: docs.flutter.io/flutter/widgets/State/context.htmlKindle
@KirollosMorkos Thanks for your answer, but the problem I'm having is not about accessing the context of the widget (see my last comment)Carbuncle
K
19

In the code you've provided, LoginScreen is not a descendant of LoginBlocProvider which is why it can't find the ancestor widget. Your code wraps the WelcomeScreen route in LoginBlocProvider, but not the whole navigator. The solution is to wrap your MaterialApp in LoginBlocProvider and then you will have access to it everywhere in your app.

class App extends StatelessWidget {
  @override
  Widget build(context) {
    return LoginBlocProvider(
      child: MaterialApp(
        title: 'Iniciar Sesion',
        home: WelcomeScreen(),
      ),
    );
  }
}
Kindle answered 11/9, 2018 at 0:27 Comment(12)
That make sense, would you mind giving some more detail about it? If i wanted to place a BlocProvider after some navigation, how can I achieve that?Carbuncle
Just updated my answer to show that! When you use a MaterialApp, it actually bundles a lot of commonly used widgets together for you, including a Navigator. By putting your LoginBlocProvider outside the MaterialApp, you can guarantee that all routes in your app will have access to it.Kindle
Thank you so much for your answer. I'll try itCarbuncle
No problem, glad I could help!Kindle
If i wanted to place a BlocProvider after some navigation, how can I achieve that? @KirollosMorkosRant
What do you mean @KrishnakumarCN?Kindle
Assume I have a module for some user logic which displays several screens after my main executes. So how can I provide a bloc only to that module and its children. If I do some navigation to display my children then the bloc will not be accessible there as per the answers understanding. Do I need to use MaterialApp parent again to my new module? Correct me if I'm wrong with the concepts. @KirollosMorkosRant
@KrishnakumarCN, as long as the Bloc is in the widget hierarchy, all of it's children will have access to it. The bloc is scoped to its descendants. In general, you shouldn't need more than one instance of MaterialApp in your widget hierarchy, but you can have several bloc instances if that makes sense in your use case.Kindle
@KirollosMorkos I'm facing the same issue. I'll try to explain what krishnakumarcn wants to say. So suppose my nagivagtion is like this - MaterialApp -> HomePage -> (A form to ask name) -> (A screen that displays that name) . In this case I will want to wrap the LogicBlocProvider around the form page, then how can I access it in the next pages ?Hefner
@AryanSethi - If the next pages you are referring to are descendants of LoginBlocProvider, you can access the bloc using LoginBlocProvider.of(context). See api.flutter.dev/flutter/widgets/… for more info.Kindle
@KirollosMorkos I am a little confused here. LoginScreen is not a descendant of LoginBlocProvider??Horsey
@KirollosMorkos Your code wraps the WelcomeScreen route in LoginBlocProvider, but not the whole navigator.?? How can I wrap the whole navigator inside LoginBlocProvider? I know wrapping the MaterialApp inside LoginBlocProvider makes it globally accessible. But, what I want is to make an InteritedWidget accessible to only a sub tree and screens that are navigated from this sub tree.Horsey
B
2

InheritedWidget should wrap the full MatrialApp widget (root widget)

Bushcraft answered 18/12, 2019 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.