Flutter Navigator 2.0: Using go_router

Go beyond Flutter’s Navigator 2.0 and learn how to handle navigation with the go_router package. By Kevin D Moore.

4.5 (15) · 9 Reviews

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

Adding Routes

Now, start by adding the first routes for your screens. Note that all the paths are defined as strings in the constants.dart file. Replace // TODO: Add Routes with:

GoRoute(
   name: rootRouteName,
   path: '/',
   redirect: (state) =>
   // TODO: Change to Home Route
    state.namedLocation(loginRouteName),
),
GoRoute(
    name: loginRouteName,
    path: '/login',
    pageBuilder: (context, state) => MaterialPage<void>(
       key: state.pageKey,
       child: const Login(),
    ),
),
GoRoute(
    name: createAccountRouteName,
    path: '/create-account',
    pageBuilder: (context, state) => MaterialPage<void>(
       key: state.pageKey,
       child: const CreateAccount(),
    ),
),
// TODO: Add Home route and children

In the code above, you define the default route, which is the '/' path, and routes for the Login and Create Account screens. The default path will redirect you to the home route with the default shop tab selected.

For each route, you need to provide a:

  1. Path — This is a string in the form of /path.
  2. Name — defined in the constants.dart file (optional).
  3. Screen widget — defined in a page builder or a redirect.

Next, define the error handler. Replace // TODO: Add Error Handler with:

errorPageBuilder: (context, state) => MaterialPage<void>(
  key: state.pageKey,
  child: ErrorPage(error: state.error),
),

This calls the ErrorPage screen (defined in the ui folder) and passes in the exception defined by state.error. Next, you’ll have to provide the MyRouter class at the top of the widget tree.

Updating Main

Open main.dart in the lib directory.

In the import section, remove the import ui/login.dart import and add:

import 'router/routes.dart';

Find and replace // TODO: Add Provider with:

Provider<MyRouter>(
  lazy: false,
  create: (BuildContext createContext) =>
      MyRouter(loginState),
),

The code above will create your MyRouter class and provide it below.

Find and replace // TODO: Add Router with:

final router = Provider.of<MyRouter>(context, listen: false).router;
return MaterialApp.router(
  routeInformationParser: router.routeInformationParser,
  routerDelegate: router.routerDelegate,

This gets your router class from Provider and uses the routeInformationParser and routerDelegate that GoRouter provides. No need to create these yourself. You should now have two return statements.

Copy the fields from old MaterialApp object and paste them into your newly created object. Make sure to find // TODO: Remove and remove home: Login(),. Your return should look like the following:

final router = Provider.of<MyRouter>(context, listen: false).router;
return MaterialApp.router(
  routeInformationParser: router.routeInformationParser,
  routerDelegate: router.routerDelegate,
    debugShowCheckedModeBanner: false,
  title: 'Navigation App',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
);

After this, your build method will look like this:

Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider<CartHolder>(
        lazy: false,
        create: (_) => CartHolder(),
      ),
      ChangeNotifierProvider<LoginState>(
        lazy: false,
        create: (BuildContext createContext) => loginState,
      ),
      Provider<MyRouter>(
        lazy: false,
        create: (BuildContext createContext) => MyRouter(loginState),
      ),
    ],
    child: Builder(
      builder: (BuildContext context) {
        final router = Provider.of<MyRouter>(context, listen: false).router;
        return MaterialApp.router(
          routeInformationParser: router.routeInformationParser,
          routerDelegate: router.routerDelegate,
          debugShowCheckedModeBanner: false,
          title: 'Navigation App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
        );
      },
    ),
  );
}

The last step before you see the magic of GoRouter is to implement these routes.

Implementing Routes

Open login.dart in the ui directory. In the import section, add:

import 'package:go_router/go_router.dart';

Find and replace // TODO: Add Create Account Route with:

context.goNamed(createAccountRouteName);

In the code above, you navigate to the Create Account screen.

GoRouter has a nice extension to the build context that allows you to call go or goNamed with the given route. Note that there are two different methods you can call:

  • go or goNamed: Replaces the current page with a new stack of the given page
  • push or pushNamed: Adds a page to the stack

Open create_account.dart in the ui directory. In the import section, add:

import 'package:go_router/go_router.dart';

Find and replace // TODO: Add Login Route with:

context.goNamed(loginRouteName);

In the code above, you navigate to the Login screen.

Hot restart. Make sure that when you tap Create Account from the Login screen, you go to the Create Account screen. And, make sure that when you tap Cancel from the Create Account screen, it takes you back to the Login screen.

Routing from Login to Create Account screen and vice-versa

From the Login or the Create Account screens, you’ll want to go to the Home screen when the user logs in or creates an account. Take a look at the saveLoginState method in login.dart file:

void saveLoginState(BuildContext context) {
  Provider.of<LoginState>(context, listen: false).loggedIn = true;
}

Here, you change the loggedIn flag to true. Since the router is listening to the LoginState class, it will refresh and display the Home screen. This won’t work yet since you haven’t hooked up the home route.

Using Redirects

Many apps have a login system. You can use the redirect section to handle the different states of your app. In this app, you’ll only deal with whether the user is logged in or not.

Return to routes.dart and replace // TODO: Add Redirect with the code below.

// redirect to the login page if the user is not logged in
redirect: (state) {
  // 1
  final loginLoc = state.namedLocation(loginRouteName);
  // 2
  final loggingIn = state.subloc == loginLoc;
  // 3
  final createAccountLoc = state.namedLocation(createAccountRouteName);
  final creatingAccount = state.subloc == createAccountLoc;
  // 4
  final loggedIn = loginState.loggedIn;
  final rootLoc = state.namedLocation(rootRouteName);

  // 5
  if (!loggedIn && !loggingIn && !creatingAccount) return loginLoc;
  if (loggedIn && (loggingIn || creatingAccount)) return rootLoc;
  return null;
},

In the code above, state.location holds the current route. Returning null means you are not redirecting and the system will use the current location.

Here are some things the code above manages:

  1. Gets the login location.
  2. Checks if the user is going to the login location.
  3. Gets the create account location and checks whether the user is going to the create account location.
  4. Checks if the user is logged in.
  5. Verifies that the current location is neither the Login screen nor the Create Account screen, and the user isn’t logged in. Then, goes to the Login screen.
  6. If the current location is either the Login or the Create Account screens and the user is logged in, goes to the Home screen.
  7. Returns null to say you are not redirecting.

To see the working of the redirects, you need to add the Home routes.

Adding Home Routes

Next, you add the Home screen route and some of its subroutes. Replace // TODO: Add Home route and children in routes.dart with the code below:

GoRoute(
    name: homeRouteName,
    // 1
    path: '/home/:tab(shop|cart|profile)',
    pageBuilder: (context, state) {
      // 2
      final tab = state.params['tab']!;
      return MaterialPage<void>(
          key: state.pageKey,
          // 3
          child: HomeScreen(tab: tab),
      );
      },
      routes: [
        GoRoute(
            name: subDetailsRouteName,
            // 4
            path: 'details/:item',
            pageBuilder: (context, state) => MaterialPage<void>(
              key: state.pageKey,
              // 5
            child: Details(description: state.params['item']!),
            ),
        ),
        GoRoute(
            name: profilePersonalRouteName,
            path: 'personal',
            pageBuilder: (context, state) => MaterialPage<void>(
              key: state.pageKey,
              child: const PersonalInfo(),
            ),
        ),
        GoRoute(
            name: profilePaymentRouteName,
            path: 'payment',
            pageBuilder: (context, state) => MaterialPage<void>(
              key: state.pageKey,
              child: const Payment(),
            ),
        ),
        GoRoute(
            name: profileSigninInfoRouteName,
            path: 'signin-info',
            pageBuilder: (context, state) => MaterialPage<void>(
              key: state.pageKey,
              child: const SigninInfo(),
            ),
        ),
        GoRoute(
            name: profileMoreInfoRouteName,
            path: 'more-info',
            pageBuilder: (context, state) => MaterialPage<void>(
              key: state.pageKey,
              child: const MoreInfo(),
            ),
        ),
      ],
  ),
    // TODO: Add Other routes

In the code above, you define the main routes. The home route uses parameters. Take a look at the path: /home/:tab(shop|cart|profile). This consists of the path home and a tab parameter that will be either shop, cart or profile. These are the three bottom buttons on the Home screen.

Here are some things that the code above handles:

  1. Defines your tab based home screen path.
  2. Gets the tab parameter.
  3. Passes the tab parameter to HomeScreen.
  4. Defines the details path that requires an item.
  5. Passes the item to Details as a description.

Find // TODO: Change to Home Route and replace it with the code below:

state.namedLocation(homeRouteName, params: {'tab': 'shop'}),

Hot restart and try tapping on the Login button on the Login screen or the Create Account button on the Create Account screen. It will take you to the Home screen.

When you’re on the Home screen, hot restart again. You’ll see that the app directly opened on the Home screen instead of the Login screen. This is the result of using redirects.