Irbisa · Шпаргалка

Навигация и маршрутизация

Navigator, именованные маршруты, go_router, передача данных между экранами

Junior-разработчик · 24 вопроса

  1. 01

    Что такое Navigator, что такое маршрут (route) и как работают push и pop?

    Navigator представляет собой виджет, который управляет стеком объектов Route: push кладёт новый экран наверх, pop снимает верхний и снова показывает экран под ним.

    // Open a screen on top of the current oneNavigator.of(context).push(  MaterialPageRoute(builder: (_) => const DetailsPage()),); // Close the top screen…
  2. 02

    Чем отличаются push, pushReplacement, pushAndRemoveUntil и popUntil и что использовать после логина?

    push кладёт маршрут наверх, pushReplacement заменяет верхний, pushAndRemoveUntil кладёт новый и снимает маршруты под ним, пока предикат не скажет «хватит», а popUntil возвращается к маршруту, который уже есть в стеке.

    // Swap only the top routeNavigator.pushReplacement(  context,  MaterialPageRoute(builder: (_) => const HomePage()),);…
  3. 03

    Как передать данные на новый экран и получить результат обратно?

    Вперёд данные передаются через конструктор нового экрана, а результат возвращается через pop со значением: push возвращает Future, который завершается тем, что второй экран передал в pop.

    // Forward: constructorNavigator.push(  context,  MaterialPageRoute(builder: (_) => DetailsPage(userId: '123')),);…
  4. 04

    ColorPickerPage закрывается через Navigator.pop(context, 'red'), но пользователь уходит системной кнопкой «назад». Что выведет этот код?

    Future<void> pickColor(BuildContext context) async {  final color = await Navigator.push<String>(    context,    MaterialPageRoute(builder: (_) => const ColorPickerPage()),  );  print('picked: $color');}

    Выведет picked: null.

  5. 05

    Как работают именованные маршруты и как обработать маршрут с параметром вроде /product/42 через onGenerateRoute?

    MaterialApp.routes сопоставляет точные имена с экранами; любое имя, которого там нет, уходит в onGenerateRoute, где вы сами разбираете имя и возвращаете Route.

    MaterialApp(  routes: {    '/': (_) => const HomePage(),    '/settings': (_) => const SettingsPage(),  },  onGenerateRoute: (settings) {…
  6. 06

    Нажатие на эту кнопку падает с «Navigator operation requested with a context that does not include a Navigator»: почему?

    class App extends StatelessWidget {  const App({super.key});   @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        body: ElevatedButton(          onPressed: () => Navigator.of(context).push(            MaterialPageRoute(builder: (_) => const NextPage()),          ),          child: const Text('Go'),        ),      ),    );  }}

    context принадлежит App, который находится выше MaterialApp, а MaterialApp создаёт Navigator внутри себя.

  7. 07

    Как showDialog, showModalBottomSheet и showMenu вписываются в стек навигации?

    Все три кладут модальный маршрут в Navigator, так что то, что выглядит как оверлей, оказывается настоящей записью в стеке: «назад» сначала закрывает его, а возвращает он то, что вы передали в Navigator.pop.

    final confirmed = await showDialog<bool>(  context: context,  barrierDismissible: false,  builder: (dialogContext) => AlertDialog(    title: const Text('Delete account?'),    actions: […
  8. 08

    Как перехватить кнопку «назад», чтобы подтвердить уход с экрана с несохранёнными изменениями (PopScope, ранее WillPopScope)?

    Оберните экран в PopScope с canPop: false, пока есть несохранённые изменения; тогда pop отклоняется, onPopInvokedWithResult вызывается с didPop == false, и вы спрашиваете пользователя и закрываете экран сами, если он подтвердил.

    class _EditProfileState extends State<EditProfilePage> {  bool _dirty = false;   @override  Widget build(BuildContext context) {    return PopScope(…
  9. 09

    Что такое Navigator 2.0 (Router API), чем он отличается от Navigator 1.0 и зачем его ввели?

    Navigator 1.0 императивный: вы вызываете push и pop.

    class _AppState extends State<App> {  Order? _selected; // the stack is a function of this state   @override  Widget build(BuildContext context) {    return Navigator(…
  10. 10

    В go_router чем отличаются context.go() и context.push()?

    go заменяет весь стек тем, который ваша таблица маршрутов задаёт для этой локации; push кладёт одну страницу поверх того, что уже есть.

    final router = GoRouter(  routes: [    GoRoute(      path: '/orders',      builder: (context, state) => const OrdersPage(),      routes: […
  11. 11

    Как сделать нижнюю навигацию, где каждая вкладка сохраняет своё состояние и свой стек «назад»?

    Дайте каждой вкладке свой Navigator и держите все вкладки живыми: в go_router это StatefulShellRoute.indexedStack, который держит по ветке на вкладку и переключается между ними, ничего не уничтожая.

    final router = GoRouter(  initialLocation: '/home',  routes: [    StatefulShellRoute.indexedStack(      builder: (context, state, shell) => Scaffold(        body: shell, // keeps every branch alive…
  12. 12

    Найдите баг: после переезда OrderPage во вкладку нижней навигации нажатие No закрывает экран заказа, а диалог остаётся открытым.

    Future<bool?> confirmCancel(BuildContext context) => showDialog<bool>(      context: context,      builder: (_) => AlertDialog(        title: const Text('Cancel this order?'),        actions: [          TextButton(            onPressed: () => Navigator.pop(context, false),            child: const Text('No'),          ),          TextButton(            onPressed: () => Navigator.pop(context, true),            child: const Text('Yes'),          ),        ],      ),    );

    Кнопки делают pop с context страницы, а не диалога.

  13. 13

    Как защитить маршруты авторизацией в go_router?

    Через redirect: go_router вызывает его перед показом любой локации, и вы возвращаете другой путь, чтобы отправить пользователя туда, или null, чтобы пропустить.

    class AuthController extends ChangeNotifier {  AuthStatus status = AuthStatus.loading; // loading, signedIn, signedOut  void update(AuthStatus s) { status = s; notifyListeners(); }} GoRouter buildRouter(AuthController auth) => GoRouter(…
  14. 14

    Найдите баг: после переключения на тёмную тему на экране настроек приложение возвращается на главный экран.

    class App extends StatelessWidget {  const App({super.key, required this.theme});  final ValueNotifier<ThemeMode> theme;   @override  Widget build(BuildContext context) => ValueListenableBuilder(        valueListenable: theme,        builder: (context, mode, _) => MaterialApp.router(          themeMode: mode,          routerConfig: GoRouter(routes: [            GoRoute(path: '/', builder: (_, __) => const HomePage()),            GoRoute(path: '/settings', builder: (_, __) => const SettingsPage()),          ]),        ),      );}

    Новый GoRouter создаётся внутри build, поэтому каждая перестройка App (здесь смена темы) передаёт в MaterialApp.router свежий роутер, который стартует со своей начальной локации, и весь стек навигации теряется.

  15. 15

    Каким будет стек после выполнения этого кода, какой экран окажется сверху и что покажет следующее «назад»?

    // the stack starts as [Home]final nav = Navigator.of(context);nav.pushNamed('/a');nav.pushNamed('/b');nav.pushNamed('/c');nav.pushReplacementNamed('/d');nav.pop();

    Стек будет [Home, A, B], сверху B: pushReplacement заменил C на D, поэтому последний pop снял D и открыл B.

  16. 16

    Как настроить deep links, Android App Links и iOS Universal Links во Flutter-приложении?

    Используйте проверяемые https-ссылки: разместите assetlinks.json и apple-app-site-association в /.well-known/ на своём домене, объявите домен в приложении и дайте MaterialApp.router получить путь; с версии 3.27 Flutter по умолчанию передаёт deep links в роутер.

    <!-- android/app/src/main/AndroidManifest.xml, inside the main activity --><intent-filter android:autoVerify="true">  <action android:name="android.intent.action.VIEW" />  <category android:name="android.intent.category.DEFAULT" />  <category android:name="android.intent.category.BROWSABLE" />  <data…
  17. 17

    Пользователь тапнул push-уведомление, и нужно открыть конкретный экран. Как навигировать, если под рукой нет BuildContext?

    Навигируйте через объект, который живёт вне дерева виджетов: GlobalKey<NavigatorState>, переданный в MaterialApp.navigatorKey, или (с go_router) сам экземпляр GoRouter, лежащий в DI-контейнере.

    final navigatorKey = GlobalKey<NavigatorState>(); MaterialApp(navigatorKey: navigatorKey, home: const HomePage()); // From a service: no context neededvoid openOrder(String id) {…
  18. 18

    Flutter web-приложение показывает URL вида example.com/#/orders/42, а после перехода на чистые пути обновление страницы даёт 404: что происходит?

    По умолчанию Flutter web использует hash-стратегию: всё после # на сервер не отправляется, поэтому подходит любой статический хостинг.

  19. 19

    Как сделать свой переход между страницами и что такое animation и secondaryAnimation?

    Используйте PageRouteBuilder (или CustomTransitionPage в go_router) и оберните страницу в свой переход внутри transitionsBuilder; animation ведёт этот маршрут при появлении и уходе, а secondaryAnimation ведёт его, пока поверх него кладут другой маршрут.

    Route<T> fadeScaleRoute<T>(Widget page) {  return PageRouteBuilder<T>(    transitionDuration: const Duration(milliseconds: 280),    reverseTransitionDuration: const Duration(milliseconds: 220),    pageBuilder: (context, animation, secondaryAnimation) => page,    transitionsBuilder: (context, animation, secondaryAnimation, child) {…
  20. 20

    Как работают Hero-анимации и какие у них типичные подводные камни?

    Оберните виджет в Hero с одинаковым tag на обоих экранах; во время перехода Flutter поднимает его в оверлей и анимирует из старой позиции и размера в новые.

    // List screenListTile(  leading: Hero(    tag: 'product-${product.id}', // unique per item    child: Image.network(product.thumbUrl, width: 56, height: 56),  ),…
  21. 21

    Как экран узнаёт, что поверх него положили другой маршрут, например, чтобы поставить видео на паузу?

    Используйте RouteObserver и подмешайте RouteAware в State экрана: didPushNext вызывается, когда маршрут перекрывают, а didPopNext вызывается, когда он снова виден.

    final routeObserver = RouteObserver<PageRoute<void>>();// MaterialApp(navigatorObservers: [routeObserver], ...) class _PlayerPageState extends State<PlayerPage> with RouteAware {  late final VideoPlayerController _controller = widget.controller;…
  22. 22

    На медленном телефоне пользователь дважды тапает по строке списка, и в стек попадают два одинаковых экрана деталей: как это предотвратить?

    Защитите навигацию сами: Navigator никогда не убирает дубликаты, потому что два маршрута из одного виджета действительно остаются двумя маршрутами.

    class _OrderListState extends State<OrderList> {  bool _navigating = false;   Future<void> _open(Order order) async {    if (_navigating) return; // second tap is ignored    _navigating = true;…
  23. 23

    Deep link на товар открывает экран товара, но «назад» закрывает приложение вместо перехода в каталог. Как восстановить нормальный стек «назад»?

    Сделайте так, чтобы URL описывал иерархию, и пусть роутер построит все страницы над целевой: в go_router вложите маршрут товара в маршрут каталога, тогда /catalog/product/42 даст [Home, Catalog, Product], и «назад» пойдёт вверх, а не наружу.

    final router = GoRouter(  routes: [    GoRoute(      path: '/',      builder: (_, __) => const HomePage(),      routes: […
  24. 24

    Android убивает приложение в фоне, и когда пользователь возвращается, стек глубиной в три экрана пропал. Как его восстановить?

    Включите state restoration во Flutter: задайте приложению restorationScopeId и открывайте экраны через restorable*-методы Navigator, тогда фреймворк сохранит стек маршрутов в ОС и пересоберёт его после убийства процесса.

    void main() => runApp(const MaterialApp(      restorationScopeId: 'app', // without this nothing is restored      home: OrderListPage(),    )); // Top-level and annotated: saved as a reference plus its arguments…