Navigation Flutter
Naviguer entre des écrans
Section titled “Naviguer entre des écrans”Flutter offre deux mécanismes de navigation :
Navigator, une API impérative couvrant les besoins de la plupart des cas d’utilisations, grâce à un widgetNavigatorRouter, une API en partie déclarative, beaucoup plus complexe, mais permettant des scénarios plus évolués, notamment sur le web
Navigator
Section titled “Navigator”L’API originelle de navigation permet essentiellement de gérer une stack de routes. La MaterialApp s’ouvre sur une route initiale, au dessus de laquelle il est ensuite possible d’empiler d’autres écrans/routes.
Le widget MaterialApp gère un Navigator, et le met à disposition grâce au principe de InheritedWidget, une sorte de Higher Order Component auquel il est possible d’accéder à partir des “écrans-enfants”.
Ce mécanisme est utilisé pour plusieurs fonctionnalités dans Flutter :
- Theme
- MediaQuery
- …
ElevatedButton.icon( label: const Text('Créer un dossier'), icon: const Icon(Icons.create_new_folder), onPressed: ()=> Navigator.of(context).push( MaterialPageRoute(builder: (context) => Screen2()), ),)Material Routes
Section titled “Material Routes”Chaque écran est défini en tant que Route (cf. MaterialRoutePage) ajoutée (push) ou retirée (pop) d’une pile de routes.
Navigator.of(context).push(route);Plutôt qu’ajouter une route à la pile, il est possible de remplacer la route actuelle par une nouvelle route.
Navigator.of(context).pushReplacement(route, result : routeResult);Pour quitter/fermer une route :
Navigator.of(context).pop(result);Navigator.of(context).popUntil(result);Named Routes
Section titled “Named Routes”Il est possible d’associer un nom à chaque route de l’application. Cette association peut être déclarée :
- via
MaterialApp.routesetMaterialApp.initialRoute - ou
MaterialApp.onGenerateRouteetMaterialApp.onGenerateInitialRoutes
MaterialApp( initialRoute: '/', routes: { '/': (context) => HomeScreen(), '/list': (context) => ListScreen(), '/form': (context) => FormScreen(), },);
class HomeScreen extends StatelessWidget { const HomeScreen({Key? key}) : super(key: key);
@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(), body: Center( child: ElevatedButton.icon( label: const Text('Afficher la liste'), icon: const Icon(Icons.list), onPressed: () => Navigator.of(context).pushNamed('/list'), ), ), );}
class ListScreen extends StatelessWidget { const ListScreen({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Liste'), TextButton( child: const Text('Retour'), onPressed: Navigator.of(context).pop, ), ], ), ), ); }}Route Arguments
Section titled “Route Arguments”Les routes nommées peuvent recevoir un argument. Il est possible d’accéder à l’argument de la route actuelle :
- dans un widget : à l’aide de
ModalRoute.of(context)!.settings.arguments - en utilisant
MaterialApp.onGenerateRoute
Navigator.of(context).pushNamed(routeName, arguments:args);Navigator.of(context).pushReplacementNamed( routeName, arguments:args, result : routeResult,);Navigator.of(context).popAndPushNamed( routeName, arguments:args, result:screenResult,);Transitions
Section titled “Transitions”Il est possible de personnaliser l’animation de transition entre les deux routes/écrans.
Navigator.of(context).push( PageRouteBuilder( pageBuilder: (context, anim1, anim2) => FormScreen(user), transitionsBuilder: (context, anim1, anim2, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; final tween = Tween(begin: begin, end: end); final offsetAnim = anim1.drive(tween); return SlideTransition( position: offsetAnim, child: child, ); }, ),)Multi-navigators
Section titled “Multi-navigators”Il est possible d’imbriquer plusieurs Navigator, de manière à créer plusieurs “piles” de navigation.
Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.close), onPressed: Navigator.of(context, rootNavigator: true).pop, ), ), body: Navigator( initialRoute: '/', onGenerateRoute: (settings) { Widget screen; switch (settings.name) { case '/page2': screen = const FormScreen2(); break; case '/page3': screen = const FormScreen3(); break; default: screen = const FormScreen1(); break; } return MaterialPageRoute(builder: (context) => screen); }, ),);Router (API 2.0)
Section titled “Router (API 2.0)”Cette nouvelle API étant particulièrement complexe, plusieurs packages ont été développés pour en offrir les avantages en s’épargnant cette complexité.
- https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade
- https://www.raywenderlich.com/19457817-flutter-navigator-2-0-and-deep-links
- Tutoriel Navigator 2
go_router
Section titled “go_router”C’est, à ce jour, le package go_router recommandé.
- navigation :
context.go/context.pushetcontext.pop - paramètres :
- liens dynamiques : ‘/item/:id’
- paramètres de requête (query parameters) : ‘/search**?query=dupont**’
- paramètre
extra: context.go(‘/path’, extra:item)
- sub-routes
- Video : Intro to go_router
Go_router config
Section titled “Go_router config”MaterialApp.routerpermet d’initialiser une MaterialApp avec un GoRouter.GoRouterimplémente l’interface permettant d’utiliser les fonctionnalités de l’API de “navigation avancée”.
class App extends StatelessWidget { const App({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp.router(routerConfig: router); }}
final router = GoRouter( routes: [ GoRoute(path: '/', builder: (context, routerState) => HomeScreen()), GoRoute(path: '/list', builder: (context, routerState) => ListScreen()), ],);Go_router navigation
Section titled “Go_router navigation”context.push(path)=> ajoute la route à la pilecontext.go(path)=> remplace la route actuelle
class HomeScreen extends StatelessWidget { const HomeScreen({super.key});
@override Widget build(BuildContext context) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text('HOME'), ElevatedButton( onPressed: () => context.push('/list'), child: Text('Push ListScreen'), ), ElevatedButton( onPressed: () => context.go('/list'), child: Text('Go to ListScreen'), ), ], ), ), );}context.pop([Object? result])=> referme la route, peut optionnellement renvoyer un résultat.context.canPop()=> renvoie true si une route est présente “derrière” la route actuelle
class ListScreen extends StatelessWidget { const ListScreen({super.key});
@override Widget build(BuildContext context) => Scaffold( appBar: AppBar( leading: GoRouter.of(context).canPop() ? IconButton(onPressed: context.pop, icon: Icon(Icons.arrow_back)) : SizedBox.shrink(), ), body: ListView( children: List.generate(12, (index) => Text('Item $index'))), );}