Во 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.
- Как обработать кнопку «назад» и что с ней изменилось.