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.
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: Using go_router
25 mins
- Getting Started
- Sneaking a Peek
- Introducing go_router
- Examining GoRouter Components
- Implementing Router
- Adding Routes
- Updating Main
- Implementing Routes
- Using Redirects
- Adding Home Routes
- Adding More Routes
- Implementing the Details Route
- Updating the Details Page
- Using Paths
- Routing in Profile Page
- Where to Go From Here?
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:
- Path — This is a string in the form of
/path
. - Name — defined in the
constants.dart
file (optional). - 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
orgoNamed
: Replaces the current page with a new stack of the given page -
push
orpushNamed
: 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.
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:
- Gets the login location.
- Checks if the user is going to the login location.
- Gets the create account location and checks whether the user is going to the create account location.
- Checks if the user is logged in.
- 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.
- If the current location is either the Login or the Create Account screens and the user is logged in, goes to the Home screen.
- 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:
- Defines your tab based home screen path.
- Gets the tab parameter.
- Passes the tab parameter to
HomeScreen
. - Defines the details path that requires an item.
- 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.