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.

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.
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.
// Raw Navigator 2.0 approach - verbose and rarely used directly
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
AppRoutePath? _currentPath;
AppRoutePath? get currentConfiguration => _currentPath;
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
},
);
}
Future<void> 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.
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:
import 'package:flutter/material.dart';
import 'router_config.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
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:
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<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
android:scheme="https"
android:host="example.com"
android:pathPrefix="/products" />
</intent-filter>
</activity>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:
{
"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.
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.
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});
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.
Gotowy na rozmowy o Flutter?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
StatefulShellRoute do zachowania stanu zakładek
Gdy użytkownicy przełączają zakładki, ShellRoute przebudowuje dziecko. StatefulShellRoute zachowuje stos nawigacji każdej gałęzi:
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ą:
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.
// 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:
import 'package:go_router/go_router.dart';
part 'routes.g.dart';
<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData {
const HomeRoute();
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
<ProductRoute>(path: '/product/:id')
class ProductRoute extends GoRouteData {
final String id;
const ProductRoute({required this.id});
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:
// 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
redirectna 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 oraz changelog GoRoutera dla funkcji specyficznych dla wersji
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Znajdziesz błąd w Flutter?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 23 sierpnia 2026
Tagi
Udostępnij
Powiązane artykuły

Optymalizacja wydajności Fluttera w 2026: Impeller, przebudowy i dobre praktyki
Jak utrzymać aplikacje Flutter na stałych 60 lub 120 fps w 2026 dzięki Impellerowi, zdyscyplinowanym przebudowom widgetów, RepaintBoundary i profilowaniu w DevTools.

Flutter Custom RenderObjects w 2026: Zaawansowane renderowanie i pytania rekrutacyjne
Kompletny przewodnik po tworzeniu własnych RenderObjects we Flutterze. Architektura trzech drzew, implementacja RenderBox, malowanie pikseli i przygotowanie do rozmów kwalifikacyjnych.

Flutter vs React Native w 2026: Architektura, Wydajność i Kiedy Wybrać Którą Technologię
Szczegółowe porównanie Flutter 3.44 i React Native 0.86 obejmujące architekturę renderowania, benchmarki wydajności, doświadczenie programisty i kwestie rekrutacyjne dla cross-platformowego rozwoju mobilnego w 2026.