# Nawigacja Flutter 2.0 i GoRouter w 2026: Deep Linking i pytania rekrutacyjne > Opanuj nawigację Flutter z GoRouter 17.5: routing deklaratywny, deep linking, ShellRoute, guardy routingu oraz pytania rekrutacyjne z praktycznymi przykładami. - Published: 2026-08-23 - Updated: 2026-08-23 - Author: Anthony Fillion-Maillet - Tags: flutter, navigation, gorouter, deep-linking, mobile - Reading time: 9 min --- Nawigacja we Flutterze przeszła znaczącą ewolucję od wprowadzenia Navigation 2.0. GoRouter 17.5, oficjalna paczka zespołu Flutter, obsługuje złożoność Router API, zapewniając jednocześnie deklaratywny routing, automatyczny deep linking oraz zagnieżdżoną nawigację od razu po instalacji. > **GoRouter jest kompletny funkcjonalnie** > > Zespół Flutter uznaje GoRouter za funkcjonalnie kompletny od 2026 roku. Obsługuje parametry ścieżki, parametry zapytań, przekierowania, ShellRoute dla persystentnego UI oraz StatefulShellRoute do zachowania stanu zakładek. Ręczne pisanie czystego Navigator 2.0 praktycznie nie występuje już w produkcji. ## Architektura Navigator 2.0 Navigator 2.0 wprowadził deklaratywne, URL-owe podejście do nawigacji Flutter. Zamiast imperatywnych wywołań `push` i `pop`, stan aplikacji determinuje, co pojawia się na stosie nawigacji. Architektura opiera się na trzech kluczowych klasach: - **Router**: Widget najwyższego poziomu koordynujący nawigację - **RouteInformationParser**: Przekształca URL-e na stan aplikacji - **RouterDelegate**: Buduje drzewo widgetów na podstawie tego stanu Gdy deep link przychodzi z systemu operacyjnego lub URL zmienia się w przeglądarce, framework aktualizuje stan, a UI reaguje. Problem polega na tym, że ręczne pisanie własnego `RouterDelegate` i `RouteInformationParser` wymaga znacznej ilości boilerplate'u. ```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(); } } ``` Ta rozwlekłość jest powodem, dla którego powstał GoRouter. Opakowuje on Navigator 2.0 i obsługuje cały boilerplate. ## Konfiguracja GoRouter 17.5 GoRouter 17.5 wprowadza wsparcie dla metadanych routów oraz ograniczenia wyrażeń regularnych dla parametrów ścieżki. Minimalne wymagania to Flutter 3.32 i 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); }, ), ], ), ], ); ``` Router integruje się z `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', ); } } ``` ## Konfiguracja Deep Linkingu dla Androida i iOS Deep linking pozwala zewnętrznym URL-om otwierać konkretne ekrany w aplikacji. GoRouter obsługuje routing automatycznie po skonfigurowaniu platformy. ### Android App Links Android wymaga `intent-filter` w `AndroidManifest.xml` oraz pliku Digital Asset Links hostowanego na domenie: ```xml ``` Plik `assetlinks.json` musi być serwowany pod adresem `https://example.com/.well-known/assetlinks.json`. ### iOS Universal Links Dla iOS należy włączyć `Associated Domains` w Xcode oraz hostować plik `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 naprawił krytyczny błąd, w którym deep linki Android cold-start z pustymi ścieżkami traciły scheme i authority. Oznacza to, że deep linki działają teraz niezawodnie nawet gdy aplikacja nie jest uruchomiona. > **Testowanie deep linków** > > Deep linki na Androidzie testuje się poleceniem `adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123"`. Na iOS należy użyć `xcrun simctl openurl booted "https://example.com/products/123"`. ## ShellRoute dla persystentnego UI nawigacji ShellRoute opakowuje routing podrzędny persystentnym elementem UI, takim jak `BottomNavigationBar` lub `Drawer`. Shell pozostaje widoczny podczas nawigacji między dziećmi. ```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 naprawił problem, w którym gesty cofania na iOS usuwały cały `ShellRoute` zamiast tylko aktywnego sub-route'a. ## StatefulShellRoute do zachowania stanu zakładek Gdy użytkownicy przełączają zakładki, `ShellRoute` przebudowuje dziecko. `StatefulShellRoute` zachowuje stos nawigacji każdej gałęzi: ```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(), ), ], ), ], ), ], ); ``` Z tą konfiguracją, jeśli użytkownik przejdzie do `/home/details/42`, przełączy się na zakładkę Explore, a następnie wróci do Home, nadal będzie na ekranie szczegółów. ## Route Guards z przekierowaniami Przekierowania obsługują sprawdzanie uwierzytelniania i warunkową nawigację. Callback `redirect` wykonuje się przed każdą nawigacją: ```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 dodał wsparcie dla użycia metod rozszerzających kontekst, takich jak `context.namedLocation()`, wewnątrz callbacków redirect. ## Typowe pytania rekrutacyjne z nawigacji Flutter Rekruterzy często testują wiedzę o nawigacji, ponieważ dotyka ona architektury, zarządzania stanem i integracji z platformą. Oto pytania, które wyróżniają doświadczonych kandydatów: **P: Jaki problem rozwiązuje Navigator 2.0, którego Navigator 1.0 nie rozwiązywał?** Navigator 1.0 używa imperatywnej nawigacji: `Navigator.push()` i `Navigator.pop()`. Aplikacja nie może reprezentować stanu nawigacji jako URL, co utrudnia deep linking i wsparcie dla weba. Navigator 2.0 jest deklaratywny: stan aplikacji determinuje stos nawigacji, URL-e są obywatelami pierwszej klasy, a deep linki działają automatycznie. **P: Kiedy użyć `context.go()` vs `context.push()`?** `context.go('/path')` zastępuje obecny stos nawigacji aż do dopasowanego route'a. `context.push('/path')` dodaje nowy route na szczycie istniejącego stosu. `go()` stosuje się do nawigacji najwyższego poziomu (przełączanie zakładek, powrót do strony głównej), a `push()` do drążenia w szczegóły przy zachowaniu nawigacji wstecz. **P: Jak zachować stan między przełączaniem zakładek?** Należy użyć `StatefulShellRoute.indexedStack()`. Utrzymuje on oddzielne stosy nawigacji dla każdej gałęzi i zachowuje je przy przełączaniu zakładek. Bez tego przełączanie zakładek przebudowuje całe poddrzewo. **P: Jak przekazywać złożone obiekty między routami?** Istnieją trzy podejścia. Po pierwsze, przekazanie ID w parametrze ścieżki i pobranie obiektu na ekranie docelowym. Po drugie, użycie parametru `extra` GoRoutera do bezpośredniego przekazania obiektu. Po trzecie, przechowywanie obiektu w rozwiązaniu do zarządzania stanem (Riverpod, Bloc) i dostęp do niego z miejsca docelowego. Pierwsze podejście jest najlepsze dla deep linkingu, ponieważ URL pozostaje udostępnialny. ```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); }, ), ``` **P: Czym jest callback `onEnter` w GoRouter 17.5?** Callback `onEnter` wykonuje się podczas wchodzenia na route i zapewnia dostęp zarówno do obecnego, jak i następnego stanu route'a. Pozwala uruchomić logikę przed pełnym załadowaniem route'a, co jest przydatne dla analityki lub warunkowego pre-loadingu. ## Bezpieczne typy routów z generowaniem kodu GoRouter wspiera routing bezpieczny typowo poprzez generowanie kodu. Definiuje się klasy danych routów, a `build_runner` generuje kod nawigacji: ```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); } ``` Po uruchomieniu `dart run build_runner build` można nawigować z bezpieczeństwem na etapie kompilacji: ```dart // Type-safe navigation ProductRoute(id: '123').go(context); ``` Eliminuje to literówki w ścieżkach routów i zapewnia, że parametry są zawsze podane. ## Opanowanie nawigacji Flutter w aplikacjach produkcyjnych - GoRouter 17.5 jest zalecanym rozwiązaniem nawigacyjnym dla aplikacji Flutter wymagających deep linkingu, zagnieżdżonej nawigacji lub wsparcia webowego - ShellRoute służy do persystentnych dolnych pasków nawigacji; StatefulShellRoute zachowuje stos nawigacji każdej zakładki - Konfiguracja deep linkingu specyficzna dla platformy (Android App Links, iOS Universal Links) jest oddzielona od GoRoutera, który obsługuje logikę routingu - Guardy uwierzytelniania implementuje się z callbackiem `redirect` na poziomie routera lub pojedynczego route'a - W scenariuszach rekrutacyjnych należy skupić się na wyjaśnieniu modelu deklaratywnego: stan aplikacji napędza UI, URL-e wywodzą się ze stanu, a GoRouter obsługuje translację - Bezpieczne typowo routy z generowaniem kodu eliminują błędy runtime z błędnie wpisanych ścieżek - Warto sprawdzić [dokumentację nawigacji Flutter](https://docs.flutter.dev/ui/navigation) oraz [changelog GoRoutera](https://pub.dev/packages/go_router/changelog) dla funkcji specyficznych dla wersji --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/flutter/flutter-navigation-gorouter-deep-linking-2026