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?
Create Account Page
The Create Account page looks almost the same as the Login page:
The only difference here is the user can use the Cancel button, tap the back arrow icon or press the device back button to go back to the Login page.
Shopping List Page
This page displays a list of items which is a hardcoded list of dummy data to simulate a shopping list:
When the user taps an item in this list, it takes them to the Details page. The item number clicked is passed to the Details page as a constructor argument.
This page also supports two actions in the AppBar, which denote Settings and Cart. Tapping these will take the user to the respective pages.
Details Page
The Details page shows item details:
This screen shows the buttons Add to Cart and Cart. The Add to Cart button will add the item to an internal list that mimics a cart, and will take the user back to the Shopping List page. The Cart button will take the user to the Cart page.
Cart Page
This page shows the items in the cart:
It has an AppBar action for navigating to the Checkout page.
Checkout Page
The Checkout page shows the items in the cart:
It has two buttons: one to go back to the Shopping List page, and another to clear the cart.
Settings Page
This page allows the user to log out:
Afterward, they’ll return to the Login page. This will also reset the logged-in
flag to false
to preserve this state.
Pages Setup
Now that you’re aware of all the pages the app displays, you’ll need some information about the pages to represent them in the app. This information shall be captured in a class named PageConfiguration
.
In the router directory, open up the Dart file named ui_pages.dart. Here you have some constants for the different paths:
const String SplashPath = '/splash';
const String LoginPath = '/login';
const String CreateAccountPath = '/createAccount';
const String ListItemsPath = '/listItems';
const String DetailsPath = '/details';
const String CartPath = '/cart';
const String CheckoutPath = '/checkout';
const String SettingsPath = '/settings';
The constants above define the paths or routes of each screen. It’s important to represent the UI for each page. This is done with an enum
:
enum Pages {
Splash,
Login,
CreateAccount,
List,
Details,
Cart,
Checkout,
Settings
}
Finally, the PageConfiguration
class mentioned earlier combines all the information about each page you defined above:
class PageConfiguration {
final String key;
final String path;
final Pages uiPage;
PageAction currentPageAction;
PageConfiguration(
{@required this.key, @required this.path, @required this.uiPage, this.currentPageAction});
}
PageConfiguration
holds two Strings, which represent the page’s key
and path
. And then a third parameter which represents the UI associated with that page using the Pages
enum you added earlier. The fourth item remembers the current page action that was used for this page.
Next is the PageConfiguration
s constants to hold information about each of the pages of the app, as shown below.
PageConfiguration SplashPageConfig =
PageConfiguration(key: 'Splash', path: SplashPath, uiPage: Pages.Splash, currentPageAction: null);
PageConfiguration LoginPageConfig =
PageConfiguration(key: 'Login', path: LoginPath, uiPage: Pages.Login, currentPageAction: null);
PageConfiguration CreateAccountPageConfig = PageConfiguration(
key: 'CreateAccount', path: CreateAccountPath, uiPage: Pages.CreateAccount, currentPageAction: null);
PageConfiguration ListItemsPageConfig = PageConfiguration(
key: 'ListItems', path: ListItemsPath, uiPage: Pages.List);
PageConfiguration DetailsPageConfig =
PageConfiguration(key: 'Details', path: DetailsPath, uiPage: Pages.Details, currentPageAction: null);
PageConfiguration CartPageConfig =
PageConfiguration(key: 'Cart', path: CartPath, uiPage: Pages.Cart, currentPageAction: null);
PageConfiguration CheckoutPageConfig = PageConfiguration(
key: 'Checkout', path: CheckoutPath, uiPage: Pages.Checkout, currentPageAction: null);
PageConfiguration SettingsPageConfig = PageConfiguration(
key: 'Settings', path: SettingsPath, uiPage: Pages.Settings, currentPageAction: null);
For the Splash page, a constant named SplashPageConfig
represents the page’s PageConfiguration
. The first parameter of PageConfiguration
represents the key 'Splash'
. The second argument represents the Splash page’s path, SplashPath
. Finally, the third argument represents the UI associated with the Splash page, i.e. Pages.Splash
.
The same is done for the other pages.
AppState
Every app needs to keep track of what state it is in. Is the user logged in? Does the user have any items in their cart? What page are they on? All of this information is stored in the AppState class. Open up app_state.dart in the lib directory. The first item is an enum for the page:
enum PageState {
none,
addPage,
addAll,
addWidget,
pop,
replace,
replaceAll
}
This defines what types of page states the app can be in. If the app is in the none
state, nothing needs to be done. If it is in the addPage
state, then a page needs to be added. To pop a page, set the page state to pop
.
Next is the page action:
class PageAction {
PageState state;
PageConfiguration page;
List<PageConfiguration> pages;
Widget widget;
PageAction({this.state = PageState.none, this.page = null, this.pages = null, this.widget = null});
}
This wraps several items that allow the router to handle a page action. If the state is addPage
, the page field will have the new page to add. The page, pages and widget are all optional fields and each are used differently depending on the page state.
The last class is AppState. This class holds the logged-in flag, shopping cart items and current page action. Look over the other fields and methods.
class AppState extends ChangeNotifier {
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
bool _splashFinished = false;
bool get splashFinished => _splashFinished;
final cartItems = [];
String emailAddress;
String password;
PageAction _currentAction = PageAction();
PageAction get currentAction => _currentAction;
set currentAction(PageAction action) {
_currentAction = action;
notifyListeners();
}
AppState() {
getLoggedInState();
}
void resetCurrentAction() {
_currentAction = PageAction();
}
void addToCart(String item) {
cartItems.add(item);
notifyListeners();
}
void removeFromCart(String item) {
cartItems.add(item);
notifyListeners();
}
void clearCart() {
cartItems.clear();
notifyListeners();
}
void setSplashFinished() {
_splashFinished = true;
if (_loggedIn) {
_currentAction = PageAction(state: PageState.replaceAll, page: ListItemsPageConfig);
} else {
_currentAction = PageAction(state: PageState.replaceAll, page: LoginPageConfig);
}
notifyListeners();
}
void login() {
_loggedIn = true;
saveLoginState(loggedIn);
_currentAction = PageAction(state: PageState.replaceAll, page: ListItemsPageConfig);
notifyListeners();
}
void logout() {
_loggedIn = false;
saveLoginState(loggedIn);
_currentAction = PageAction(state: PageState.replaceAll, page: LoginPageConfig);
notifyListeners();
}
void saveLoginState(bool loggedIn) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool(LoggedInKey, loggedIn);
}
void getLoggedInState() async {
final prefs = await SharedPreferences.getInstance();
_loggedIn = prefs.getBool(LoggedInKey);
if (_loggedIn == null) {
_loggedIn = false;
}
}
}