Provider: Provider.of fails when using in tangent with Navigator.push

Created on 25 Jun 2019  路  7Comments  路  Source: rrousselGit/provider

I am currently creating my Provider instance on a StatelessWidget (First Screen)

Like so

return Provider<MqttModel>(
      builder: (BuildContext context) => MqttModel(
            this.clientId,
          ),
      dispose: (BuildContext context, MqttModel mqttModel) {
        print("Is DISPOSED");
        mqttModel.disconnect();
      },

When I navigate to the next screen and call MqttModel this throws an error (Second Screen)

Consumer<MqttModel>(
          builder: (BuildContext context, value, _) {
            return ListTile();
          },
        ),

Error

The following ProviderNotFoundError was thrown building Consumer<MqttModel>(dirty):
I/flutter ( 3472): Error: Could not find the correct Provider<MqttModel> above this Consumer<MqttModel> Widget
I/flutter ( 3472): To fix, please:
I/flutter ( 3472):   * Ensure the Provider<MqttModel> is an ancestor to this Consumer<MqttModel> Widget
I/flutter ( 3472):   * Provide types to Provider<MqttModel>
I/flutter ( 3472):   * Provide types to Consumer<MqttModel>
I/flutter ( 3472):   * Provide types to Provider.of<MqttModel>()
I/flutter ( 3472):   * Always use package imports. Ex: `import 'package:my_app/my_code.dart';
I/flutter ( 3472):   * Ensure the correct `context` is being used.

I added a print line (REBUILDING) on the build method to check if the UI is getting rebuilt but that is not the case. What am I doing wrong?

OTHER INFO

This works perfectly throughout the application

runApp(
    MultiProvider(
      providers: <SingleChildCloneableWidget>[
        // Syncing Image and Files from Github
        ChangeNotifierProvider<SyncFilesystem>(
            builder: (BuildContext context) => fs),

        // KeyValue (Shared Preferences)
        Provider<KeyValue>(
          builder: (BuildContext context) => KeyValue(prefs),
        ),

        Provider<ChatAuth>(
          builder: (BuildContext context) => ChatAuth(),
        ),

        // END
      ],
      child: MyApp(),
    ),
  );

Most helpful comment

Providers are scoped.
If they are inserted _inside_ a route, other routes cannot access the value.

If you need other routes to access that value, you need to include the provider in other routes too. Alternatively, put the provider above all routes (typically above MaterialApp) like you did in "other info".

All 7 comments

Providers are scoped.
If they are inserted _inside_ a route, other routes cannot access the value.

If you need other routes to access that value, you need to include the provider in other routes too. Alternatively, put the provider above all routes (typically above MaterialApp) like you did in "other info".

Thank you for your response. This cleared up the behavior of InheritedWidgets also for me.

I am a bit late but I found a solution on how to keep the value of a Provider alive after a Navigator.push() without having to put the Provider above the MaterialApp.

To do so, I have used the library custom_navigator. It allows you to create a Navigator wherever you want in the tree.

You will have to create 2 different GlobalKey<NavigatorState> that you will give to the MaterialApp and CustomNavigator widgets. These keys will allow you to control what Navigator you want to use.

Here is a small snippet to illustrate how to do

class App extends StatelessWidget {

   GlobalKey<NavigatorState> _mainNavigatorKey = GlobalKey<NavigatorState>(); // You need to create this key for the MaterialApp too

   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         navigatorKey: _mainNavigatorKey;  // Give the main key to the MaterialApp
         home: Provider<bool>.value(
            value: myProviderFunction(),
            child: Home(),
         ),
      );
   }

}

class Home extends StatelessWidget {

   GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>(); // You need to create this key to control what navigator you want to use

   @override
   Widget build(BuildContext context) {

      final bool myBool = Provider.of<bool>(context);

      return CustomNavigator (
         // CustomNavigator is from the library 'custom_navigator'
         navigatorKey: _navigatorKey,  // Give the second key to your CustomNavigator
         pageRoute: PageRoutes.materialPageRoute,
         home: Scaffold(
            body: FlatButton(
               child: Text('Push'),
               onPressed: () {
                  _navigatorKey.currentState.push(  // <- Where the magic happens
                     MaterialPageRoute(
                        builder: (context) => SecondHome(),
                     ),
                  },
               ),
            ),
         ),
      );   
   }
}

class SecondHome extends StatelessWidget {

   @override
   Widget build(BuildContext context) {

      final bool myBool = Provider.of<bool>(context);

      return Scaffold(
         body: FlatButton(
            child: Text('Pop'),
            onPressed: () {
               Novigator.pop(context);
            },
         ),
      );
   }

}

Here you can read the value myBool from the Provider in the Home widget but also ine the SecondHome widget even after a Navigator.push().

However, the Android back button will trigger a Navigator.pop() from the Navigator of the MaterialApp. If you want to use the CustomNavigator's one, you can do this:

// In the Home Widget insert this
   ...
   @override
   Widget build(BuildContext context) {
      return WillPopScope(
         onWillPop: () async {
            if (_navigatorKey.currentState.canPop()) {
               _navigatorKey.currentState.pop();  // Use the custom navigator when available
               return false;  // Don't pop the main navigator
            } else {
               return true;  // There is nothing to pop in the custom navigator anymore, use the main one
            }
         },
         child: CustomNavigator(...),
      );
   }
   ...

Hope it helps !

Any solution about how to reinject the provider value when changing the route?

@ggirotto reinsert the provider or move the provider above MaterialApp

@ggirotto

You can do something like this

...

final YourModel yourModel = Provider.of<YourModel>(context);

Navigator.push(
  MaterialRoutePage(
    builder: (context) => ListenableProvider<YourModel>.value(
      value: yourModel,
      child: YourNewScreen(),
    ),
  ),
);

then you can access YourModel in YourNewScreen.

Is this problem related to this? How can I keep using a provider after moving to another route, when using a navigatorKey on the MaterialApp in order to navigate rather than using the current BuildContext? Using the navigatorKey makes it possible to push/pop screens from inside my provider classes, but now each new screen doesn't know the providers of the previous screen. This was working before I used a navigatorKey.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

guopeng1994 picture guopeng1994  路  4Comments

wemped picture wemped  路  5Comments

ykaito21 picture ykaito21  路  3Comments

prasadsunny1 picture prasadsunny1  路  6Comments

Vadaski picture Vadaski  路  3Comments