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.

3.9 (26) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

Create Account Page

The Create Account page looks almost the same as the Login page:

Create Account 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:

Shopping List page

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:

Item Details page

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:

Cart page

It has an AppBar action for navigating to the Checkout page.

Checkout Page

The Checkout page shows the items in the cart:

Checkout page

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:

Settings page

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 PageConfigurations 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;
    }
  }
}