# Flutter Navigation 2.0 та GoRouter у 2026: Deep Linking та питання співбесід > Опанування навігації Flutter з GoRouter 17.5: декларативний роутинг, deep linking, ShellRoute, guard'и маршрутів та питання співбесід з практичними прикладами. - Published: 2026-08-23 - Updated: 2026-08-23 - Author: Anthony Fillion-Maillet - Tags: flutter, navigation, gorouter, deep-linking, mobile - Reading time: 9 min --- Навігація у Flutter зазнала значної еволюції з моменту впровадження Navigation 2.0. GoRouter 17.5, офіційний пакет команди Flutter, тепер обробляє складність Router API, надаючи декларативний роутинг, автоматичний deep linking та вкладену навігацію одразу після встановлення. > **GoRouter є функціонально завершеним** > > Команда Flutter вважає GoRouter функціонально завершеним станом на 2026 рік. Він підтримує параметри шляху, параметри запитів, перенаправлення, ShellRoute для персистентного UI та StatefulShellRoute для збереження стану вкладок. Написання чистого Navigator 2.0 вручну практично не зустрічається у продакшені. ## Архітектура Navigator 2.0 Navigator 2.0 запровадив декларативний, URL-орієнтований підхід до навігації Flutter. Замість імперативних викликів `push` та `pop`, стан додатку визначає, що з'являється на стеку навігації. Архітектура спирається на три ключові класи: - **Router**: Віджет верхнього рівня, що координує навігацію - **RouteInformationParser**: Перетворює URL-адреси на стан додатку - **RouterDelegate**: Будує дерево віджетів на основі цього стану Коли deep link надходить від операційної системи або URL змінюється у веб-браузері, фреймворк оновлює стан, і UI реагує. Проблема полягає в тому, що написання власного `RouterDelegate` та `RouteInformationParser` вручну вимагає значної кількості шаблонного коду. ```dart // raw_navigator2_example.dart // Raw Navigator 2.0 approach - verbose and rarely used directly class AppRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { @override final GlobalKey navigatorKey = GlobalKey(); AppRoutePath? _currentPath; @override AppRoutePath? get currentConfiguration => _currentPath; @override Widget build(BuildContext context) { return Navigator( key: navigatorKey, pages: [ const MaterialPage(child: HomeScreen()), if (_currentPath?.isProductPage == true) MaterialPage(child: ProductScreen(id: _currentPath!.productId!)), ], onDidRemovePage: (page) { // Handle page removal }, ); } @override Future setNewRoutePath(AppRoutePath path) async { _currentPath = path; notifyListeners(); } } ``` Ця багатослівність є причиною існування GoRouter. Він обгортає Navigator 2.0 та обробляє весь шаблонний код. ## Налаштування GoRouter 17.5 GoRouter 17.5 впроваджує підтримку метаданих маршрутів та обмеження регулярних виразів для параметрів шляху. Мінімальні вимоги — Flutter 3.32 та Dart 3.8. ```dart // router_config.dart import 'package:go_router/go_router.dart'; final goRouter = GoRouter( initialLocation: '/', debugLogDiagnostics: true, // Useful during development routes: [ GoRoute( path: '/', name: 'home', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'products/:id', // Path parameter name: 'product', builder: (context, state) { final productId = state.pathParameters['id']!; return ProductScreen(productId: productId); }, ), GoRoute( path: 'search', name: 'search', builder: (context, state) { // Query parameters: /search?q=flutter&category=books final query = state.uri.queryParameters['q'] ?? ''; final category = state.uri.queryParameters['category']; return SearchScreen(query: query, category: category); }, ), ], ), ], ); ``` Роутер інтегрується з `MaterialApp.router`: ```dart // main.dart import 'package:flutter/material.dart'; import 'router_config.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: goRouter, title: 'GoRouter Demo', ); } } ``` ## Налаштування Deep Linking для Android та iOS Deep linking дозволяє зовнішнім URL-адресам відкривати конкретні екрани у додатку. GoRouter обробляє роутинг автоматично після налаштування платформи. ### Android App Links Android вимагає `intent-filter` у `AndroidManifest.xml` та файл Digital Asset Links, розміщений на домені: ```xml ``` Файл `assetlinks.json` повинен бути доступний за адресою `https://example.com/.well-known/assetlinks.json`. ### iOS Universal Links Для iOS необхідно увімкнути `Associated Domains` у Xcode та розмістити файл `apple-app-site-association`: ```json // https://example.com/.well-known/apple-app-site-association { "applinks": { "apps": [], "details": [ { "appID": "TEAMID.com.example.app", "paths": ["/products/*", "/search"] } ] } } ``` GoRouter 17.5 виправив критичну помилку, коли deep links Android cold-start з порожніми шляхами втрачали scheme та authority. Це означає, що deep links тепер працюють надійно навіть коли додаток не запущено. > **Тестування deep links** > > Deep links на Android тестуються командою `adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123"`. Для iOS використовується `xcrun simctl openurl booted "https://example.com/products/123"`. ## ShellRoute для персистентного UI навігації ShellRoute обгортає дочірні маршрути персистентним елементом UI, таким як `BottomNavigationBar` або `Drawer`. Shell залишається видимим під час навігації між дочірніми елементами. ```dart // shell_router_config.dart final goRouter = GoRouter( initialLocation: '/home', routes: [ ShellRoute( builder: (context, state, child) { return ScaffoldWithNavBar(child: child); }, routes: [ GoRoute( path: '/home', name: 'home', builder: (context, state) => const HomeTab(), ), GoRoute( path: '/explore', name: 'explore', builder: (context, state) => const ExploreTab(), ), GoRoute( path: '/profile', name: 'profile', builder: (context, state) => const ProfileTab(), ), ], ), ], ); // scaffold_with_nav_bar.dart class ScaffoldWithNavBar extends StatelessWidget { final Widget child; const ScaffoldWithNavBar({super.key, required this.child}); @override Widget build(BuildContext context) { return Scaffold( body: child, bottomNavigationBar: BottomNavigationBar( currentIndex: _calculateSelectedIndex(context), onTap: (index) => _onItemTapped(index, context), items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), BottomNavigationBarItem(icon: Icon(Icons.explore), label: 'Explore'), BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'), ], ), ); } int _calculateSelectedIndex(BuildContext context) { final location = GoRouterState.of(context).uri.toString(); if (location.startsWith('/explore')) return 1; if (location.startsWith('/profile')) return 2; return 0; } void _onItemTapped(int index, BuildContext context) { switch (index) { case 0: context.go('/home'); case 1: context.go('/explore'); case 2: context.go('/profile'); } } } ``` GoRouter 17.5 виправив проблему, коли жести назад на iOS видаляли весь `ShellRoute` замість лише активного підмаршруту. ## StatefulShellRoute для збереження стану вкладок Коли користувачі перемикають вкладки, `ShellRoute` перебудовує дочірній елемент. `StatefulShellRoute` зберігає стек навігації кожної гілки: ```dart // stateful_shell_config.dart final goRouter = GoRouter( initialLocation: '/home', routes: [ StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return ScaffoldWithNavBar(navigationShell: navigationShell); }, branches: [ StatefulShellBranch( routes: [ GoRoute( path: '/home', builder: (context, state) => const HomeTab(), routes: [ GoRoute( path: 'details/:id', builder: (context, state) => DetailsScreen( id: state.pathParameters['id']!, ), ), ], ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/explore', builder: (context, state) => const ExploreTab(), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/profile', builder: (context, state) => const ProfileTab(), ), ], ), ], ), ], ); ``` З цією конфігурацією, якщо користувач переходить до `/home/details/42`, перемикається на вкладку Explore, а потім повертається до Home, він все ще буде на екрані деталей. ## Route Guards з перенаправленнями Перенаправлення обробляють перевірки автентифікації та умовну навігацію. Callback `redirect` виконується перед кожною навігацією: ```dart // authenticated_router.dart final goRouter = GoRouter( initialLocation: '/', redirect: (context, state) { final isLoggedIn = AuthService.instance.isLoggedIn; final isLoggingIn = state.matchedLocation == '/login'; // Redirect to login if not authenticated if (!isLoggedIn && !isLoggingIn) { return '/login?redirect=${state.uri}'; } // Redirect away from login if already authenticated if (isLoggedIn && isLoggingIn) { final redirect = state.uri.queryParameters['redirect']; return redirect ?? '/'; } return null; // No redirect }, routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/login', builder: (context, state) => const LoginScreen(), ), GoRoute( path: '/admin', redirect: (context, state) { // Route-level redirect for admin-only access if (!AuthService.instance.isAdmin) { return '/'; } return null; }, builder: (context, state) => const AdminScreen(), ), ], ); ``` GoRouter 17.5 додав підтримку використання методів розширення контексту, таких як `context.namedLocation()`, всередині callback'ів redirect. ## Типові питання співбесід з навігації Flutter Інтерв'юери часто перевіряють знання навігації, оскільки ця тема охоплює архітектуру, керування станом та інтеграцію з платформою. Ось питання, що вирізняють досвідчених кандидатів: **П: Яку проблему вирішує Navigator 2.0, яку не вирішував Navigator 1.0?** Navigator 1.0 використовує імперативну навігацію: `Navigator.push()` та `Navigator.pop()`. Додаток не може представити стан навігації як URL, що ускладнює deep linking та підтримку вебу. Navigator 2.0 є декларативним: стан додатку визначає стек навігації, URL-адреси є повноцінними сутностями, а deep links працюють автоматично. **П: Коли використовувати `context.go()` проти `context.push()`?** `context.go('/path')` замінює поточний стек навігації до відповідного маршруту. `context.push('/path')` додає новий маршрут на вершину існуючого стеку. `go()` використовується для навігації верхнього рівня (перемикання вкладок, перехід на головну), а `push()` — для заглиблення в деталі зі збереженням навігації назад. **П: Як зберегти стан між перемиканням вкладок?** Використовується `StatefulShellRoute.indexedStack()`. Він підтримує окремі стеки навігації для кожної гілки та зберігає їх при перемиканні вкладок. Без нього перемикання вкладок перебудовує все піддерево. **П: Як передавати складні об'єкти між маршрутами?** Існує три підходи. Перший — передати ID у параметрі шляху та отримати об'єкт на цільовому екрані. Другий — використати параметр `extra` GoRouter для безпосередньої передачі об'єкта. Третій — зберігати об'єкт у рішенні для керування станом (Riverpod, Bloc) та отримувати доступ до нього з місця призначення. Перший підхід найкращий для deep linking, оскільки URL залишається придатним для поширення. ```dart // Passing via extra (not deep-linkable) context.push('/product', extra: productObject); // Receiving via extra GoRoute( path: '/product', builder: (context, state) { final product = state.extra as Product?; return ProductScreen(product: product); }, ), ``` **П: Що таке callback `onEnter` у GoRouter 17.5?** Callback `onEnter` виконується при вході на маршрут і надає доступ як до поточного, так і до наступного станів маршруту. Він дозволяє виконувати логіку перед повним завантаженням маршруту, що корисно для аналітики або умовного попереднього завантаження. ## Типобезпечні маршрути з генерацією коду GoRouter підтримує типобезпечний роутинг через генерацію коду. Визначаються класи даних маршрутів, а `build_runner` генерує код навігації: ```dart // routes.dart import 'package:go_router/go_router.dart'; part 'routes.g.dart'; @TypedGoRoute(path: '/') class HomeRoute extends GoRouteData { const HomeRoute(); @override Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); } @TypedGoRoute(path: '/product/:id') class ProductRoute extends GoRouteData { final String id; const ProductRoute({required this.id}); @override Widget build(BuildContext context, GoRouterState state) => ProductScreen(productId: id); } ``` Після виконання `dart run build_runner build` можна навігувати з безпекою на етапі компіляції: ```dart // Type-safe navigation ProductRoute(id: '123').go(context); ``` Це усуває друкарські помилки у шляхах маршрутів та гарантує, що параметри завжди надані. ## Опанування навігації Flutter для продакшн-додатків - GoRouter 17.5 є рекомендованим рішенням для навігації у Flutter-додатках, що потребують deep linking, вкладеної навігації або підтримки вебу - ShellRoute використовується для персистентних нижніх панелей навігації; StatefulShellRoute зберігає стек навігації кожної вкладки - Платформо-специфічна конфігурація deep linking (Android App Links, iOS Universal Links) налаштовується окремо від GoRouter, який обробляє логіку роутингу - Guard'и автентифікації реалізуються за допомогою callback'у `redirect` на рівні роутера або окремого маршруту - У сценаріях співбесід слід зосередитися на поясненні декларативної моделі: стан додатку керує UI, URL-адреси виводяться зі стану, а GoRouter обробляє трансляцію - Типобезпечні маршрути з генерацією коду усувають runtime-помилки від неправильно введених шляхів - Варто перевірити [документацію навігації Flutter](https://docs.flutter.dev/ui/navigation) та [changelog GoRouter](https://pub.dev/packages/go_router/changelog) для функцій, специфічних для версії --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/flutter/flutter-navigation-gorouter-deep-linking-2026