Flutter Navigation 2.0 et GoRouter en 2026 : Deep Linking et Questions d'Entretien
Maîtriser la navigation Flutter avec GoRouter 17.5 : routage déclaratif, deep linking, ShellRoute, guards de route et questions d'entretien avec exemples pratiques.

La navigation Flutter a considérablement évolué depuis l'introduction de Navigation 2.0. GoRouter 17.5, le package officiel de l'équipe Flutter, gère désormais la complexité de l'API Router tout en fournissant un routage déclaratif, le deep linking automatique et la navigation imbriquée prêts à l'emploi.
L'équipe Flutter considère GoRouter comme fonctionnellement complet depuis 2026. Il prend en charge les paramètres de chemin, les paramètres de requête, les redirections, ShellRoute pour une interface utilisateur persistante et StatefulShellRoute pour préserver l'état des onglets. Le Navigator 2.0 brut n'est plus guère écrit manuellement en production.
Comprendre l'Architecture de Navigator 2.0
Navigator 2.0 a introduit une approche déclarative et pilotée par les URL pour la navigation Flutter. Au lieu d'appels impératifs push et pop, l'état de l'application détermine ce qui apparaît dans la pile de navigation. L'architecture repose sur trois classes fondamentales :
- Router : Le widget de niveau supérieur qui coordonne la navigation
- RouteInformationParser : Traduit les URL en état d'application
- RouterDelegate : Construit l'arbre de widgets en fonction de cet état
Lorsqu'un deep link arrive du système d'exploitation ou qu'une URL change dans un navigateur web, le framework met à jour l'état et l'interface réagit. Le problème : écrire un RouterDelegate et un RouteInformationParser personnalisés manuellement nécessite un code boilerplate conséquent.
// 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();
}
}Cette verbosité explique l'existence de GoRouter. Il encapsule Navigator 2.0 et gère le code boilerplate.
Configuration de GoRouter 17.5
GoRouter 17.5 introduit le support des métadonnées de route et les contraintes par expressions régulières pour les paramètres de chemin. Le SDK minimum requis est Flutter 3.32 et 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);
},
),
],
),
],
);Le router s'intègre avec 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',
);
}
}Configuration du Deep Linking pour Android et iOS
Le deep linking permet aux URL externes d'ouvrir des écrans spécifiques dans l'application. GoRouter gère automatiquement le routage une fois la configuration de la plateforme en place.
Android App Links
Android nécessite un intent-filter dans AndroidManifest.xml et un fichier Digital Asset Links hébergé sur le domaine :
<!-- 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>Le fichier assetlinks.json doit être servi à l'adresse https://example.com/.well-known/assetlinks.json.
iOS Universal Links
Pour iOS, la capacité Associated Domains doit être activée dans Xcode, et un fichier apple-app-site-association doit être hébergé :
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.app",
"paths": ["/products/*", "/search"]
}
]
}
}GoRouter 17.5 a corrigé un bug critique où les deep links Android lors d'un démarrage à froid avec des chemins vides perdaient le schéma et l'autorité. Cela signifie que les deep links fonctionnent désormais de manière fiable même lorsque l'application n'est pas en cours d'exécution.
Pour tester les deep links Android, utiliser adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123". Pour iOS, utiliser xcrun simctl openurl booted "https://example.com/products/123".
ShellRoute pour une Interface de Navigation Persistante
ShellRoute enveloppe les routes enfants avec un élément d'interface utilisateur persistant comme une BottomNavigationBar ou un Drawer. Le shell reste visible lors de la navigation entre ses enfants.
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 a corrigé un problème où les gestes de retour iOS faisaient sortir l'intégralité du ShellRoute au lieu de la sous-route active uniquement.
Prêt à réussir tes entretiens Flutter ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
StatefulShellRoute pour Préserver l'État des Onglets
Lorsque les utilisateurs changent d'onglet, ShellRoute reconstruit l'enfant. StatefulShellRoute préserve la pile de navigation de chaque branche :
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(),
),
],
),
],
),
],
);Avec cette configuration, si un utilisateur navigue vers /home/details/42, passe à l'onglet Explorer, puis revient à Accueil, il sera toujours sur l'écran de détails.
Guards de Route avec Redirect
Les redirections gèrent les vérifications d'authentification et la navigation conditionnelle. Le callback redirect s'exécute avant chaque navigation :
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 a ajouté le support de l'utilisation des méthodes d'extension de contexte comme context.namedLocation() dans les callbacks de redirection.
Questions d'Entretien Courantes sur la Navigation Flutter
Les recruteurs testent fréquemment les connaissances en navigation car elles touchent l'architecture, la gestion d'état et l'intégration de la plateforme. Voici des questions qui distinguent les candidats seniors :
Q : Quel problème Navigator 2.0 résout-il que Navigator 1.0 ne résout pas ?
Navigator 1.0 utilise la navigation impérative : Navigator.push() et Navigator.pop(). L'application ne peut pas représenter l'état de navigation comme une URL, rendant le deep linking et le support web difficiles. Navigator 2.0 est déclaratif : l'état de l'application détermine la pile de navigation, les URL sont des citoyens de première classe, et les deep links fonctionnent automatiquement.
Q : Quand utiliser context.go() vs context.push() ?
context.go('/path') remplace la pile de navigation actuelle jusqu'à la route correspondante. context.push('/path') ajoute une nouvelle route au-dessus de la pile existante. Utiliser go() pour la navigation de niveau supérieur (changement d'onglets, retour à l'accueil) et push() pour approfondir les détails tout en préservant la navigation arrière.
Q : Comment préserver l'état lors des changements d'onglets ?
Utiliser StatefulShellRoute.indexedStack(). Il maintient des piles de navigation séparées pour chaque branche et les préserve lors des changements d'onglets. Sans cela, changer d'onglet reconstruit l'intégralité du sous-arbre.
Q : Comment passer des objets complexes entre les routes ?
Trois approches existent. Premièrement, passer un ID dans le paramètre de chemin et récupérer l'objet sur l'écran de destination. Deuxièmement, utiliser le paramètre extra de GoRouter pour passer l'objet directement. Troisièmement, stocker l'objet dans une solution de gestion d'état (Riverpod, Bloc) et y accéder depuis la destination. La première approche est la meilleure pour le deep linking car l'URL reste partageable.
// 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);
},
),Q : Qu'est-ce que le callback onEnter dans GoRouter 17.5 ?
Le callback onEnter s'exécute lors de l'entrée dans une route et donne accès à la fois aux états de route actuel et suivant. Il permet d'exécuter de la logique avant que la route soit complètement chargée, utile pour l'analytics ou le pré-chargement conditionnel.
Routes Type-Safe avec Génération de Code
GoRouter prend en charge le routage type-safe grâce à la génération de code. Il suffit de définir des classes de données de route et de laisser build_runner générer le code de navigation :
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);
}Après avoir exécuté dart run build_runner build, la navigation se fait avec une sécurité à la compilation :
// Type-safe navigation
ProductRoute(id: '123').go(context);Cela élimine les fautes de frappe dans les chemins de route et garantit que les paramètres sont toujours fournis.
Maîtriser la Navigation Flutter pour les Applications en Production
- GoRouter 17.5 est la solution de navigation recommandée pour les applications Flutter nécessitant le deep linking, la navigation imbriquée ou le support web
- Utiliser
ShellRoutepour les barres de navigation inférieures persistantes ; utiliserStatefulShellRoutepour préserver la pile de navigation de chaque onglet - Configurer le deep linking spécifique à la plateforme (Android App Links, iOS Universal Links) séparément de GoRouter, qui gère la logique de routage
- Implémenter les guards d'authentification avec le callback
redirectau niveau du router ou au niveau de chaque route - Pour les scénarios d'entretien, se concentrer sur l'explication du modèle déclaratif : l'état de l'application pilote l'interface, les URL sont dérivées de l'état, et GoRouter gère la traduction
- Les routes type-safe avec génération de code éliminent les erreurs d'exécution dues aux chemins mal typés
- Consulter la documentation de navigation Flutter et le changelog de GoRouter pour les fonctionnalités spécifiques à chaque version
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tu saurais repérer le bug en Flutter ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 23 août 2026
Tags
Partager
Articles similaires

Optimisation des performances Flutter en 2026 : Impeller, reconstructions et bonnes pratiques
Comment maintenir des applications Flutter à 60 ou 120 fps constants en 2026 grâce à Impeller, des reconstructions de widgets maîtrisées, RepaintBoundary et le profilage DevTools.

Flutter Impeller en 2026 : Architecture du moteur de rendu, gains de performance et questions d'entretien
Guide complet sur Flutter Impeller en 2026 : architecture du moteur de rendu, élimination du shader jank, benchmarks de performance et questions techniques pour les entretiens d'embauche.

State Management Flutter : Riverpod vs BLoC - Guide Comparatif Complet
Comparaison approfondie entre Riverpod 3.x et BLoC 9.x pour la gestion d'état Flutter. Architecture, retry automatique, testabilité et cas d'usage pour choisir la meilleure solution.