Search code examples
flutterdartflutter-provider

Could not find the correct provider above this widget


I have a problem using Flutter Provider... My flow is like this: After login user id is passed to new widget -> from there it preforms save to db and then it redirects to new widget (Dashboard).

And this is a code of a widget after Login:

return MaterialApp(
      title: title,
      home: Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          body: ListView(
            children: <Widget>[
              Container(
                margin: EdgeInsets.all(8.0),
                child: Card(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(Radius.circular(8.0))),
                  child: InkWell(
                    onTap: () {
                      var user = Provider.of<UserRepository>(context);
                      user.savePreference(user.user.id, "Something");
                      user.navigateToNewPage(Dashboard(), context);
                      print(user.user.id);
                    },

This works:

user.savePreference(user.user.id, "Something");

But this is causing a problem:

user.navigateToNewPage(Dashboard(), context);

In Dashboard widget I am creating this:

 Widget build(BuildContext context) {
    var user = Provider.of<UserRepository>(context);

And in UserRepository I have this:

class UserRepository with ChangeNotifier {
  User user;
  Status _status = Status.Uninitialized;

  Status get status => _status;
  User get getUser => user;

  UserRepository.instance();

Future<void> navigateToNewPage(Widget page, BuildContext context) {
    Navigator.push(context, MaterialPageRoute(builder: (context) => page));
  }

I know this topic has already solved questions, but could not find anything suitable to my problem.


Solution

  • Provider Scope

    MaterialApp
     > provider(Screen A)
     > Screen B
    

    If Provider is instantiated in Screen A, it won't be accessible in Screen B after a Navigator.push from A → B.

    Why?

    Because Provider is an InheritedWidget and Navigator uses MaterialApp context outside its Screen A context scope. (See Details below.)

    Fix

    Moving Provider up to a common-parent, MaterialApp context, allows both Screen A and B to inherit its state/context.

    provider(MaterialApp)
     > Screen A
     > Screen B
    

    Example

    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        /// wrap MaterialApp in Provider widget
        return ChangeNotifierProvider(
          create: (context) => ColorModel(), // ← create/init your state model
          child: MaterialApp(
              home: ScreenA()
          ),
        );
      }
    }
    

    Details

    Provider

    • Provider is based on InheritedWidget. Only child widgets can inherit parent widget's state.

      • Provider needs to be the root widget for any widget tree that wants access to your "provided" state object.

    Navigator

    • Navigator.push(context) on Screen A doesn't use the context from Screen A.
      • It uses context from MaterialApp.
    • Navigator.push(context) is actually Navigator.of(context).push
    • Navigator.of(context) means: search up this context hierarchy until you find a context that instantiated a Navigator
      • A default Navigator is instantiated in MaterialApp.
      • Unless you explicitly create/use a different Navigator, you're using the default.
      • Navigator's context is that of MaterialApp.
    • Screen B will get that context (of MaterialApp), not the context of Screen A.
      • B is a sibling of A, not its child.
      • B does not inherit from A, even though it appears "instantiated" inside context A.
      • Screen B is a child of MaterialApp context, not of Screen A context.
      • Provider context scope, if defined in Screen A, doesn't cover Screen B

    Screen A → B

    Navigator.push(context, MaterialPageRoute(builder: (context) => ScreenB()))
    

    is actually:

    Navigator.of(context).push(MaterialPageRoute(builder: (context) => ScreenB()))
    

    which is like:

    Navigator.of(MaterialApp).push(
      MaterialPageRoute(builder: (MaterialAppContext) => ScreenB())
    )
    

    So Screen B is under MaterialApp context, not under Screen A context and therefore has no access to Screen A Provider and its context.

    See this answer for a code sample to a similar question about Provider.