* Update sample index dependencies * Update to tuneup 0.3.8, update dependencies * Upgrade to null safety, lock sass version * fix analyzer warnings * Fix unit tests * Fix issues from upgrading to null safety
Navigation and Routing
A sample that shows how to use the Router API to handle common navigation scenarios.
Goals
- Demonstrate common navigation scenarios:
- Parsing path parameters ('/user/:id')
- Sign in (validation / guards)
- Nested navigation
- Provide a reusable implementation of RouterDelegate and RouteInformationParser
- Demonstrate how deep linking is configured on iOS and Android
- Demonstrate how to use the Link widget from
package:url_Launcherwith the Router API.
How it works
The top-level widget, Bookstore, sets up the state for this app. It places
three InheritedNotifier widgets in the tree: RouteStateScope,
BookstoreAuthScope, and LibraryScope, which provide the state for the
application:
RouteState: stores the current route path (/book/1) as aParsedRouteobject (see below).BookstoreAuthScope: stores a mock authentication API,BookstoreAuth.LibraryScope: stores the data for the app,Library.
The Bookstore widget also uses the MaterialApp.router()
constructor to opt-in to the Router API. This constructor requires a
RouterDelegate and RouteInformationParser. This app uses the
routing.dart library, described below.
routing.dart
This library contains a general-purpose routing solution for medium-sized apps. It implements these classes:
SimpleRouterDelegate: ImplementsRouterDelegate. UpdatesRouteStatewhen a new route has been pushed to the application by the operating system. Also notifies theRouterwidget whenever theRouteStatechanges.TemplateRouteParser: Implements RouteInformationParser. Parses the incoming route path into aParsedRouteobject. ARouteGuardcan be provided to guard access to certain routes.ParsedRoute: Contains the current route location ("/user/2"), path parameters ({id: 2}), query parameters ("?search=abc"), and path template ("/user/:id")RouteState: Stores the currentParsedRoute.RouteGuard: Guards access to routes. Can be overridden to redirect the incoming route if a condition isn't met.
App Structure
The SimpleRouterDelegate constructor requires a WidgetBuilder parameter and
a navigatorKey. This app uses a BookstoreNavigator widget, which configures
a Navigator with a list of pages, based on the current RouteState.
SimpleRouterDelegate(
routeState: routeState,
navigatorKey: navigatorKey,
builder: (context) => BookstoreNavigator(
navigatorKey: navigatorKey,
),
);
This Navigator is configured to display either the sign-in screen or the
BookstoreScaffold. An additional screen is stacked on top of the
BookstoreScaffold if a book or author is currently selected:
return Navigator(
key: widget.navigatorKey,
onPopPage: (route, dynamic result) {
// ...
},
pages: [
if (routeState.route.pathTemplate == '/signin')
FadeTransitionPage<void>(
key: signInKey,
child: SignInScreen(),
),
else ...[
FadeTransitionPage<void>(
key: scaffoldKey,
child: BookstoreScaffold(),
),
if (selectedBook != null)
MaterialPage<void>(
key: bookDetailsKey,
child: BookDetailsScreen(
book: selectedBook,
),
)
else if (selectedAuthor != null)
MaterialPage<void>(
key: authorDetailsKey,
child: AuthorDetailsScreen(
author: selectedAuthor,
),
),
],
],
);
The BookstoreScaffold widget uses package:adaptive_navigation to build a
navigation rail or bottom navigation bar based on the size of the screen. The
body of this screen is BookstoreScaffoldBody, which configures a nested
Navigator to display either the AuthorsScreen, SettingsScreen, or
BooksScreen widget.
Linking vs updating RouteState
There are two ways to change the current route, either by updating RouteState,
which the RouterDelegate listens to, or use the Link widget from
package:url_launcher. The SettingsScreen widget demonstrates both options:
Link(
uri: Uri.parse('/book/0'),
builder: (context, followLink) {
return TextButton(
child: const Text('Go directly to /book/0 (Link)'),
onPressed: followLink,
);
},
),
TextButton(
child: const Text('Go directly to /book/0 (RouteState)'),
onPressed: () {
RouteStateScope.of(context)!.go('/book/0');
},
),
Questions/issues
If you have a general question about the Router API, the best places to go are:
If you run into an issue with the sample itself, please file an issue in the main Flutter repo.