Как пройти провайдера с навигатором?

Мне нужен провайдер (Model2) от homepage до Page2, поэтому, когда пользователь вернется к homepage (onWillPop), я могу сделать вызов API от провайдера (Model2) и обновить homepage.

Но когда я звоню _onPaidBackPress(context), возникает ошибка:

Необработанное исключение: ошибка: не удалось найти правильный поставщик над этим потребительским виджетом

StatefulWidget на главной странице:

@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)));

На странице 2:

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();
            },
          ),
        ],
),
      );
    },
  );
}

Спасибо за помощь!


person FlutterFirebase    schedule 21.08.2019    source источник


Ответы (3)


Вы можете легко передать поставщика в навигаторе, создав нового поставщика при переходе на новую страницу.

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

Здесь либо создайте новый объект InventoryItem(), либо передайте существующий объект поставщика.

person rahul Kushwaha    schedule 01.09.2020
comment
Это кажется самым простым решением. Действительно хороший ответ! Спасибо ???? - person K-Dawg; 25.10.2020

Вам нужно будет сделать так, чтобы поставщик отображался над виджетом навигатора, чтобы он был привязкой текущего виджета. Это означает, что он должен располагаться над MaterialApp(), CupertinoApp() или любым другим Directionality(), которое вы можете использовать.

Пожалуйста, обратитесь к следующим ссылкам для получения дополнительной информации о InheritedWidget.

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

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

person Daniel Szy    schedule 21.08.2019
comment
Ваш ответ противоречит сам себе: provider является InheritedWidget. - person Rémi Rousselet; 21.08.2019
comment
@ RémiRousselet Как вы можете решить эту проблему? - person FlutterFirebase; 21.08.2019
comment
@ RémiRousselet, вы правы, я использовал его в контексте, где значение Provider.value было бы неизменным, а значение создавалось бы во время выполнения, поэтому размещение его над приложением материала не имеет смысла. Я отредактировал ответ, чтобы убрать путаницу. - person Daniel Szy; 21.08.2019
comment
@DanielSzy Спасибо за ответ! Но я не считаю это правильным. Ваше решение возможно. Но You don’t want to place ChangeNotifierProvider higher than necessary (because you don’t want to pollute the scope). Так что мне лучше положить ниже. @ RémiRousselet говорит, что можно передать provider с context в Navigator. Я ищу, как это сделать. - person FlutterFirebase; 21.08.2019
comment
Если вы найдете решение, напишите здесь свой ответ. Я пытался сделать это около 3 часов, но обнаружил, что это невероятно беспорядочно, и к тому же это будет изменчивым, не так ли? Мы не хотим вручную обрабатывать экземпляры, которые передаются с контекстом. Это по сути сломало бы основную ценность, которую поставщик добавляет нам, разработчикам, не так ли? - person Daniel Szy; 22.08.2019
comment
Спасибо! Я хочу попробовать пройти этот путь, потому что @ RémiRousselet считает, что это правильный способ. Я тоже очень долго стараюсь делать. Вы можете опубликовать, как вы это сделали (даже если это выглядит неаккуратно)? - person FlutterFirebase; 22.08.2019
comment
По сути, он просто передавал текущее значение поставщика в маршрут в качестве аргумента, а затем упаковывал новую страницу в новый поставщик. Это, как я уже сказал, не служит цели провайдера imho. Если вы не хотите перестраивать дерево виджетов, я предлагаю вам поднять поставщика и использовать ListenableProvider для перестройки зависимых виджетов. - person Daniel Szy; 22.08.2019
comment
Не знаю, есть ли другой выход, но это правильный ответ. Мой InheritedWidget был на шаг ниже MaterialApp, и он не работал. После того, как я поместил InheritedWidget вверху и MaterialApp в качестве дочернего элемента, context.dependOnInheritedWidgetOfExactType начал работать в виджетах Navigator.push. - person mileusna; 02.01.2020

Я немного опоздал, но нашел решение, как сохранить значение Provider в живых после Navigator.push() без необходимости помещать Provider над MaterialApp.

Для этого я использовал библиотеку custom_navigator. Это позволяет вам создавать Navigator в любом месте дерева.

Вам нужно будет создать 2 разных GlobalKey<NavigatorState>, которые вы передадите виджетам MaterialApp и CustomNavigator. Эти клавиши позволят вам контролировать, какой навигатор вы хотите использовать.

Вот небольшой фрагмент, чтобы проиллюстрировать, как это сделать

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);
            },
         ),
      );
   }

}

Здесь вы можете прочитать значение myBool из Provider в виджете Home, а также в виджете SecondHome даже после Navigator.push().

Однако кнопка Android back вызовет Navigator.pop() из навигатора MaterialApp. Если вы хотите использовать CustomNavigator, вы можете сделать это:

// 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(...),
      );
   }
   ...
person Valentin Vignal    schedule 05.08.2020