How to pass provider with Navigator?
Asked Answered
M

4

19

I need pass provider (Model2) from homepage to Page2 so when user go back to homepage (onWillPop) I can make API call from provider (Model2) and update homepage.

But when I call _onPaidBackPress(context) there is error:

Unhandled Exception: Error: Could not find the correct Provider above this Consumer Widget

StatefulWidget in homepage:

@override
  Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<Model1, Model2>(
initialBuilder: (_) => Model2(),
  builder: (_, model1, model2) => model2
    ..string = model1.string,
),
  child: Consumer<Model2>(
    builder: (context, model2, _) =>

...
                        await Navigator.push(
                            context,
                            new MaterialPageRoute(
                                builder: (BuildContext context) =>
                                    new Page2(
                                        context: context)));

In Page2:

class Page2 extends StatefulWidget {

  final BuildContext context;

  Page2({Key key, this.context}) : super(key: key);

  @override
  State createState() => new Page2State(context: context);
}

class Page2State extends State<Page2> {

  final context;

  ChatScreenState({Key key, this.context});

@override
  Widget build(BuildContext context) {

    return Consumer<Model1>(
      builder: (context, model, _) {

        return new WillPopScope(
              onWillPop: () => model.isPaid ? _onPaidBackPress(context) : _onBackPressed(context),




Future<void> _onPaidBackPress(context) async {


final model2 = Provider.of<Model2>(context, listen: false);

  return showDialog<void>(
    context: context,
    barrierDismissible: false, 
    builder: (BuildContext context) {
      return 


    Provider.value(value: model2, child:


AlertDialog(
        title: Text('Back'),
        content: SingleChildScrollView(
          child: ListBody(
            children: <Widget>[
              Text('Go back'),
            ],
          ),
        ),
        actions: <Widget>[
          FlatButton(
            child: Text('OK'),
            onPressed: () async {

await model2.getData();

              Navigator.of(context).pop();
            },
          ),
        ],
),
      );
    },
  );
}

Thanks for help!

Makings answered 21/8, 2019 at 19:22 Comment(0)
M
19

You can easily pass the provider in the Navigator by creating a new provider when Navigating to a new page.

 Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) => Provider(
          create: (context) => InventoryItem(),
          builder: (context, child) => ProductDetails(),
        ),
      ),
    );

Here either create a new object InventoryItem() or pass the existing provider object.

Myotome answered 1/9, 2020 at 14:35 Comment(2)
This seems like the simplest solution. Really nice answer! Thank you πŸ™ – Revue
Notice that you actually have to pass a newly created InventoryItem. If you pass the existing provider object, it will get destroyed once you navigate back from the route, and other widgets will throw an error. – Entail
M
3

You would need to make the Provider appear above the Navigator Widget to be an anchestor of the current Widget. That means, it must reside above the MaterialApp(), CupertinoApp() or whatever Directionality() you might use.

Please refer to following links for more information about InheritedWidget.

https://medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1

https://www.youtube.com/watch?v=Zbm3hjPjQMk

Microbe answered 21/8, 2019 at 19:50 Comment(8)
Your answer contradicts itself: provider is an InheritedWidget. – Ryswick
@RémiRousselet What is you solution for this issue? – Makings
@RémiRousselet you are right, I was using it in a context where Provider.value would be immutable and the value would be instantiated on runtime, so putting it above the material app would not make sense. I edited the answer to remove confusion. – Microbe
@DanielSzy Thanks for reply! But I not think this correct. You solution is possible. But You don’t want to place ChangeNotifierProvider higher than necessary (because you don’t want to pollute the scope). So it is better for me put lower down. @RémiRousselet say it is possible pass provider with context in Navigator. I am look for how to do this. – Makings
Please post your answer here if you find the solution. I tried to do that for like 3 hours but found it incredibly messy, and also it would be mutable then right? We don't want to manually handle instances that get passed with context. That would essentially break the core value provider adds to us developers, no? – Microbe
Thanks! I want try pass this way because @RémiRousselet say this is correct way for do this. I am also try very long to do. You can post how you did it (even if it look messy)? – Makings
It was essentially only passing the current value of the provider to the route as an argument, then wrapping the new page in a new provider. This does as I said not serve the purpose of provider imho. If you don't want to rebuild your widget tree, I suggest you lift the provider up and use ListenableProvider to rebuild depending widgets. – Microbe
I don't know is there any other way but this is the correct answer. My InheritedWidget was one step below MaterialApp and it didn't work. After I have put InheritedWidget on the top and MaterialApp as a child, context.dependOnInheritedWidgetOfExactType started working in Navigator.push widgets. – Eschatology
F
2

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(...),
      );
   }
   ...
Formwork answered 5/8, 2020 at 17:18 Comment(0)
M
1

In order to pass the current instance of a provider on the next route, we can use Provider.value. For ChangeNotifier we can do,

final value = context.read<Counter>();
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ChangeNotifierProvider<Counter>.value(
      value: value,
      builder: (context, child) {
        return const NextPage();
      },
    ),
  ),
);

A complete example I've included on this post

Mccomas answered 30/5, 2023 at 8:46 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.