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:
-
location
fromrouteInformation
is 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. Useparse
fromUri
to create aUri
from 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
pathSegements
list of theuri.
- Then return the
PageConfiguration
corresponding 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
ShoppingBackButtonDispatcher
extendRootBackButtonDispatcher
. - Declare a
final
instance ofShoppingRouterDelegate
. This helps you link the dispatcher to the app'sRouterDelegate
, i.e.ShoppingRouterDelegate
. - Delegate
didPopRoute
to_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.