You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/navigation_and_routing/README.md

5.2 KiB

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_Launcher with 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 a ParsedRoute object (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: Implements RouterDelegate. Updates RouteState when a new route has been pushed to the application by the operating system. Also notifies the Router widget whenever the RouteState changes.
  • TemplateRouteParser: Implements RouteInformationParser. Parses the incoming route path into a ParsedRoute object. A RouteGuard can 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 current ParsedRoute.
  • 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.