Flutter Navigator 2.0 and Deep Links
With Flutter’s Navigator 2.0, learn how to handle deep links in Flutter and gain the ultimate navigation control for your app. By Kevin D Moore.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Flutter Navigator 2.0 and Deep Links
40 mins
- Getting Started
- Navigator 1.0
- Navigator 2.0
- Pages Overview
- Login Page
- Create Account Page
- Shopping List Page
- Details Page
- Cart Page
- Checkout Page
- Settings Page
- Pages Setup
- AppState
- RouterDelegate
- Implementing build
- Removing Pages
- Creating and Adding a Page
- Modifying the Contents
- RouteInformationParser
- Root Widget and Router
- Navigating Between Pages
- Splash Page Navigation
- BackButtonDispatcher
- Deep Linking
- Parse Deep Link URI
- Testing Android URIs
- Where to Go From Here?
RouteInformationParser
A RouteInformationParser is a delegate used by Router to parse a route’s information into a configuration of any type T which in your case would be PageConfiguration.
This app’s RouteInformationParser is also known as ShoppingParser. Make this class extend from RouteInformationParser. To begin, create a new Dart file, shopping_parser.dart, in the router directory and add the following code to this file:
import 'package:flutter/material.dart';
import 'ui_pages.dart';
class ShoppingParser extends RouteInformationParser<PageConfiguration> {
}
RouterInformationParser requires that its subclasses override parseRouteInformation and restoreRouteInformation.
parseRouteInformation converts the given route information into parsed data — PageConfiguration in this case — to pass to RouterDelegate:
@override
Future<PageConfiguration> parseRouteInformation(
RouteInformation routeInformation) async {
// 1
final uri = Uri.parse(routeInformation.location);
// 2
if (uri.pathSegments.isEmpty) {
return SplashPageConfig;
}
// 3
final path = uri.pathSegments[0];
// 4
switch (path) {
case SplashPath:
return SplashPageConfig;
case LoginPath:
return LoginPageConfig;
case CreateAccountPath:
return CreateAccountPageConfig;
case ListItemsPath:
return ListItemsPageConfig;
case DetailsPath:
return DetailsPageConfig;
case CartPath:
return CartPageConfig;
case CheckoutPath:
return CheckoutPageConfig;
case SettingsPath:
return SettingsPageConfig;
default:
return SplashPageConfig;
}
}
Here’s what’s happening in the code above:
-
locationfromrouteInformationis a String that represents the location of the application. The string is usually in the format of multiple string identifiers with slashes between — for example: `/`, `/path` or `/path/to/the/app`. It’s equivalent to the URL in a web application. UseparsefromUrito create aUrifrom this String. - If there are no paths, which is most likely the case when the user is launching the app, return
SplashPage. - Otherwise, get the first path segment from the
pathSegementslist of theuri. - Then return the
PageConfigurationcorresponding to this first path segment.
restoreRouteInformation isn't required if you don't opt for the route information reporting, which is mainly used for updating browser history for web applications. If you decide to opt in, you must also override this method to return RouteInformation based on the provided PageConfiguration.
So, override restoreRouteInformation. In a way, this method does the exact opposite of the previously defined parseRouteInformation by taking in a PageDate and returning an object of type RouteInformation:
@override
RouteInformation restoreRouteInformation(PageConfiguration configuration) {
switch (configuration.uiPage) {
case Pages.Splash:
return const RouteInformation(location: SplashPath);
case Pages.Login:
return const RouteInformation(location: LoginPath);
case Pages.CreateAccount:
return const RouteInformation(location: CreateAccountPath);
case Pages.List:
return const RouteInformation(location: ListItemsPath);
case Pages.Details:
return const RouteInformation(location: DetailsPath);
case Pages.Cart:
return const RouteInformation(location: CartPath);
case Pages.Checkout:
return const RouteInformation(location: CheckoutPath);
case Pages.Settings:
return const RouteInformation(location: SettingsPath);
default:
return const RouteInformation(location: SplashPath);
}
}
This method uses uiPage from Page to return a RouteInformation with its location set to the given path. Notice that there's a RouteInformation with the location of SplashPath in case there are no matches for uiPage.
Root Widget and Router
Now that you have all the required Router classes, hook them up with the root widget of your app in the main.dart file. Open main.dart and find:
// TODO Create Delegate, Parser and Back button Dispatcher
Define instances of ShoppingRouterDelegate and ShoppingParser.
ShoppingRouterDelegate delegate;
final parser = ShoppingParser();
Then, replace // TODO Setup Router & dispatcher with the following:
// 1
delegate = ShoppingRouterDelegate(appState);
// 2
delegate.setNewRoutePath(SplashPageConfig);
In the code above, you:
- Create the delegate with the appState field.
- Set up the initial route of this app to be the Splash page using
setNewRoutePath.
Add any needed imports. For most Flutter apps, you might have MaterialApp or CupertinoApp as the root widget. Both of these use WidgetsApp internally. WidgetsApp creates a Router or a Navigator internally. In case of a Router, the Navigator is configured via the provided routerDelegate. Navigator then manages the pages list that updates the app's navigation whenever this list of pages changes.
Since Navigator 2.0 is backward-compatible with Navigator 1.0, the easiest way to start with Navigator 2.0 is to use MaterialApp's MaterialApp.router(...) constructor. This requires you to provide instances of a RouterDelegate and a RouteInformationParser as the ones discussed above.
Hence, in the root widget's build method, replace MaterialApp with:
child: MaterialApp.router(
title: 'Navigation App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
routerDelegate: delegate,
routeInformationParser: parser,
),);
Notice that you're passing in the created routerDelegate and routeInformationParser. Run the app to make sure it still works.
Navigating Between Pages
Now you'll cover how to navigate between the various pages.
Open splash.dart from the ui folder and find appState.setSplashFinished(). If you go to setSplashFinished() you will see:
void setSplashFinished() {
// 1
_splashFinished = true;
if (_loggedIn) {
// 2
_currentAction = PageAction(state: PageState.replaceAll, page: ListItemsPageConfig);
} else {
// 3
_currentAction = PageAction(state: PageState.replaceAll, page: LoginPageConfig);
}
notifyListeners();
}
- Set the splash state to be finished.
- If the user is logged in, show the list page.
- Otherwise show the login page.
By setting the current action and calling notifyListeners, you will trigger a state change and the router will update its list of pages based on the current app state.
BackButtonDispatcher
Navigator 2.0 also uses a BackButtonDispatcher class to handle system back button presses. If you want to create a custom dispatcher, you can create a subclass of RootBackButtonDispatcher.
This app's BackButtonDispatcher is also known as ShoppingBackButtonDispatcher. Create this class by creating a new Dart file named back_dispatcher.dart in the router directory and adding the following code:
import 'package:flutter/material.dart';
import 'router_delegate.dart';
// 1
class ShoppingBackButtonDispatcher extends RootBackButtonDispatcher {
// 2
final ShoppingRouterDelegate _routerDelegate;
ShoppingBackButtonDispatcher(this._routerDelegate)
: super();
// 3
Future<bool> didPopRoute() {
return _routerDelegate.popRoute();
}
}
In the code above:
- Make
ShoppingBackButtonDispatcherextendRootBackButtonDispatcher. - Declare a
finalinstance ofShoppingRouterDelegate. This helps you link the dispatcher to the app'sRouterDelegate, i.e.ShoppingRouterDelegate. - Delegate
didPopRouteto_routerDelegate.
Note that this class doesn't do any complex back button handling here. Rather, it's just an example of subclassing RootBackButtonDispatcher to create a custom Back Button Dispatcher. If you need to do some custom back button handling, add your code to didPopRoute().
To use this class open main.dart, add the import statement and add the initializing code after final parser = ShoppingParser();:
ShoppingBackButtonDispatcher backButtonDispatcher;
Then initialize backButtonDispatcher in _MyAppState after delegate.setNewRoutePath(SplashPage);:
backButtonDispatcher = ShoppingBackButtonDispatcher(delegate);
Finally, use this dispatcher in your router in the build method by adding it before the routerDelegate: delegate, statement:
backButtonDispatcher: backButtonDispatcher,
This time, use Hot Restart instead of Hot Reload to restart the app. Then, observe there aren't changes to the flow of navigation because, as mentioned earlier, the didPopRoute in ShoppingBackButtonDispatcher does nothing special.