Flutter Navigator не работает

У меня есть приложение с двумя экранами, и я хочу сделать переход с 1-го на второй экран, нажав кнопку.

Экран 1

import 'package:flutter/material.dart';
import './view/second_page.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new MainScreen();
  }
}

class MainScreen extends State<MyApp> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        home: new Scaffold(
            appBar: new AppBar(
                title: new Text("Title")
            ),
            body: new Center(
                child: new FlatButton(child: new Text("Second page"),
                    onPressed: () {
                      Navigator.push(context,
                          new MaterialPageRoute(
                              builder: (context) => new SecondPage()))
                    }
                )
            )
        )
    );
  }
}

Экран 2

import 'package:flutter/material.dart';

class SecondPage extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return new SecondPageState();
  }
}


class SecondPageState extends State<SecondPage> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Title"),
      ),
      body: new Center(
        child: new Text("Some text"),
      ),
    );
  }
}

Толчок не происходит, и я получил это

The following assertion was thrown while handling a gesture: Navigator operation requested with a context that does not include a Navigator. The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.

Another exception was thrown: Navigator operation requested with a context that does not include a Navigator.

Что не так?

15
0
25 161
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

MainScreen    <------ context
  --> MaterialApp
   (--> Navigator built within MaterialApp)
      --> Scaffold
        --> App Bar
          --> ...
        --> Center
          --> FlatButton

Поэтому, когда вы используете контекст для поиска навигатора, вы используете контекст для MainScreen, который не находится под навигатором.

Вы можете создать новый подкласс Stateless или Stateful Widget, который будет содержать ваш Center + FlatButton, поскольку функция сборки в них будет указывать на этот уровень, или вы можете использовать Строитель и определить обратный вызов builder (который имеет контекст, указывающий на Builder), чтобы вернуть Center + FlatButton.

Есть две основные причины, по которым маршрут не может быть найден.

1) Маршрут определен. ниже контекста передается в Navigator.of (context) - сценарий, который объяснил @rmtmackenzie

2) Маршрут определяется как на родственной ветви, например.

Root 

  -> Content (Routes e.g. Home/Profile/Basket/Search)

  -> Navigation (we want to dispatch from here)

Если мы хотим отправить маршрут из виджета Navigation, мы должны знать ссылку на NavigatorState. Наличие глобальной ссылки обходится дорого, особенно когда вы собираетесь перемещать виджет по дереву. https://docs.flutter.io/flutter/widgets/GlobalKey-class.html. Используйте его только там, где нет возможности получить его из Navigator.of (context).

Чтобы использовать GlobalKey внутри MaterialApp, определите navigatorKey

final navigatorKey = GlobalKey<NavigatorState>();

Widget build(BuildContext context) => MaterialApp {
    navigatorKey: navigatorKey
    onGenerateRoute : .....
};

Теперь в любом месте приложения, где вы передаете navigatorKey, вы можете вызывать

navigatorKey.currentState.push(....);

Только что написал об этом https://medium.com/@swav.kulinski/flutter-navigating-off-the-charts-e118562a36a5

Использование GlobalKey относительно дорого, и его следует избегать, если это вообще возможно - хотя вы используете его может, это не означает, что вы используете должен, если только у вас нет конкретного варианта использования, который требует этого. И я бы сказал, что если вам действительно нужно передать его, вы, вероятно, сможете провести рефакторинг своего кода, чтобы этого избежать. Вы определенно не должны его распространять! Весь механизм Nativator.of(context) предназначен для того, чтобы избежать необходимости передавать вещи.

rmtmckenzie 03.07.2018 19:04

Не могли бы вы объяснить, почему передача ссылки дороже обхода дерева?

robotoaster 05.07.2018 12:13

Передача ссылки обходится дешево - это скорее тот факт, что GlobalKey сам по себе является тяжелым решением ... и все, что называется «Global», вероятно, не очень хорошо = D. Также из документации flutter: Global keys are relatively expensive. У них также может быть странное поведение - см. этот другой вопрос. Но TBH - с точки зрения реальной производительности на голом железе, это, вероятно, быстрее, чем использование Navigator.of, которое составляет O (N), где N - глубина виджета (хотя N не должно быть таким большим).

rmtmckenzie 05.07.2018 12:23

В моем предыдущем комментарии я намеревался сказать, что GlobalKeys - это относительно дорогой тип ключа ... и независимо от этого следует избегать использования ключей, если они вам абсолютно не нужны. Итак, мое предыдущее утверждение вводит в заблуждение, я не могу вернуться и отредактировать его ... В любом случае, Flutter имеет InheritedWidget или прямую передачу данных из виджета в суб-виджет и Widget.of () для возврата к родительскому виджету. Как общая архитектура, флаттер поощряет передачу (неизменяемых) данных вниз и состояние вверх. GlobalKey подрывает это, позволяя передавать состояние вниз.

rmtmckenzie 05.07.2018 12:28

Я согласен с тем, что в контексте примера - можно избежать использования GlobalKey, поскольку разделение дерева на виджеты сделает возможным доступ к Navigator из контекста. Однако есть сценарии, в которых это не поможет, например Навигатор находится в дочерней ветви виджета, который хочет его использовать. Я обновлю свой ответ.

robotoaster 05.07.2018 12:34

Это справедливо - есть случаи, когда вам нужно использовать GlobalKey, но если вы обнаружите, что используете его, вы должны хотя бы подумать, можете ли вы провести рефакторинг, чтобы этого избежать. Хотя технически ... если вы не делаете что-то особенное (что может случиться, я делаю), у вас обычно не должно быть чего-либо в дочерних ветвях вашего навигатора - вы должны создавать экземпляры дочерних виджетов на каждой `` странице '' ( т.е. каждый pages в большинстве примеров флаттера имеет каркас) и позволяет флаттеру определять переходы между ними. Но это выходит за рамки этого вопроса & answer = D.

rmtmckenzie 05.07.2018 12:42

Просто сделайте класс MaterialApp в основном методе, как в этом примере

 void main() => runApp(MaterialApp(home: FooClass(),));

Он отлично работает для меня, Я надеюсь это сработает с тобой

Красивое элегантное решение.

Naveed Ahmad 28.09.2020 17:27

Существует еще один способ решения этой проблемы, если вы используете Диспетчер сигналов тревоги (Android) и снова открываете свое приложение. Если перед навигацией вы не включили экран, навигатор никогда не заработает. Хотя это редкое использование, я думаю, что об этом стоит знать.

Убедитесь, что таблица маршрутов указана в том же контексте:

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
       home: FutureBuilder(future: _isUserLoggedIn(),
                   builder: (ctx, loginSnapshot) => 
                   loginSnapshot.connectionState == ConnectionState.waiting ?
                   SplashScreen() : loginSnapshot.data == true ?  AppLandingScreen(): SignUpScreen()
                ),
       routes: {
         AppLandingScreen.routeName: (ctx) => AppLandingScreen(),
      },

    );



  }

Я столкнулся с этой проблемой, потому что я определяю таблицу маршрутов в другом методе сборки.

Я новичок и потратил два дня, пытаясь преодолеть привязку объекта Navigtor к черному экрану.

Проблема, вызывающая это, была дублированные фиктивные данные. Найдите Bellow два фиктивных блока данных:

**Problematic data **- duplicate assets/image:

_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate2.png', 'Spring bowl', '\$13'),
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate5.png', 'Berry bowl', '\$34'),

**Solution **- after eliminating duplicated image argument:
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate2.png', 'Spring bowl', '\$13'),
_buildFoodItem('assets/plate6.png', 'Avocado bowl', '\$34'),

Я надеюсь, что это поможет кому-то,,,,,,,

Если навигатор не работает, это может быть вызвано многими причинами, но главная из них заключается в том, что навигатор не находит контекст. Итак, чтобы решить эту проблему, попробуйте обернуть свой виджет внутри Строитель, потому что у конструктора есть свой собственный контекст ...

Другие вопросы по теме