Урок по треку Flutter и Dart 6 мин

Навигация: Navigator, go_router, deep links

Navigator, маршруты, go_router, deep links и обработка «назад».

Во Flutter два API навигации. Императивный Navigator (его называют Navigator 1.0) прост и подходит для большинства переходов. Декларативный Router API (Navigator 2.0) нужен для веба, диплинков и сложных сценариев, но напрямую им почти не пользуются: берут go_router, который поддерживает команда Flutter.

Navigator: стек маршрутов

Navigator хранит стек Route. Верхний виден на экране.

final result = await Navigator.of(context).push<bool>(
  MaterialPageRoute(builder: (_) => const EditPage()),
);
// на EditPage: Navigator.of(context).pop(true);
  • push возвращает Future, который завершится значением из pop. Так возвращают результат с экрана.
  • pushReplacement заменяет верхний маршрут: экран входа после успешного логина.
  • pushAndRemoveUntil(route, (r) => false) очищает стек: переход на главную после выхода.
  • popUntil возвращается до нужного маршрута.

Именованные маршруты (Navigator.pushNamed и таблица routes) документация больше не рекомендует для большинства приложений: они плохо работают с диплинками и в вебе.

Navigator.of(context) ищет ближайший Navigator выше. Во вложенной навигации (свой стек в каждой вкладке) это может быть не тот Navigator, что вы ждёте. Для корневого используют rootNavigator: true.

go_router

Маршруты описываются как конфигурация, URL становится источником правды:

final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (_, __) => const Home(), routes: [
      GoRoute(path: 'item/:id', builder: (_, s) => Item(id: s.pathParameters['id']!)),
    ]),
  ],
  redirect: (context, state) {
    final loggedIn = auth.isLoggedIn;
    return !loggedIn && state.matchedLocation != '/login' ? '/login' : null;
  },
  refreshListenable: auth,
);
MaterialApp.router(routerConfig: router);
  • context.go('/item/42') переходит на адрес и строит стек по иерархии маршрутов (будет Home, над ним Item).
  • context.push('/item/42') кладёт экран поверх текущего стека.
  • redirect с refreshListenable реализует защиту маршрутов: разлогинились, и роутер сам перевёл на вход.
  • ShellRoute и StatefulShellRoute держат общую оболочку (нижнюю панель) и отдельные стеки вкладок с сохранением состояния.

Deep links

Диплинк открывает приложение сразу на нужном экране. На Android это App Links (intent-filter с autoVerify и файл assetlinks.json на домене), на iOS Universal Links (Associated Domains и apple-app-site-association). Схемы вида myapp:// проще, но их может перехватить другое приложение. С декларативной навигацией диплинк превращается в обычный адрес маршрута, поэтому go_router обрабатывает их без дополнительного кода.

Кнопка «назад»

WillPopScope устарел. Теперь используется PopScope: canPop: false запрещает уход, а колбэк onPopInvokedWithResult сообщает о попытке, чтобы показать диалог «Сохранить изменения?». Это нужно и для предиктивного жеста «назад» на Android 14+, при котором система должна заранее знать, можно ли уйти.

Передача данных

Простые параметры через конструктор экрана или параметры пути. Тяжёлые объекты через extra в go_router не переживут перезапуск и не работают в вебе по прямой ссылке: надёжнее передать id и загрузить данные на экране.

Частые ошибки

  • Navigator.of(context) после await без проверки mounted.
  • Навигация прямо в build: вызывайте её в обработчиках или слушателях.
  • Передача объектов вместо идентификаторов, из-за чего ломаются диплинки.
  • Путать go и push в go_router: разные стеки.

Что спросят на собесе

  • Как вернуть результат с экрана.
  • Navigator 1.0 и Router API: в чём разница, зачем go_router.
  • Как сделать защиту маршрутов для неавторизованных.
  • Как настроить диплинки на Android и iOS.
  • Как обработать кнопку «назад» и что с ней изменилось.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.