Understanding Routing in Flutter Using GoRouter

Aashish dahal
readytowork, Inc.
Published in
6 min readJan 21, 2023

In this article, we learn how to handle navigation using the Go router package develop by flutter.dev.

Go Router
The purpose of the go_router package is to use declarative routes to reduce complexity, regardless of the platform you’re targeting (mobile, web, desktop), handle deep and dynamic linking from Android, iOS, and the web, along with several other navigation-related scenarios, while still (hopefully) providing an easy-to-use developer experience.

Getting Started

First, you need to add dependency/plugin in pubspec.yaml to your project:

dependencies:
go_router: ^5.2.4 # or the latest version from pub.dev

After adding the dependency, you can now use the go router package in your Dart code by importing this file:

import ‘package:go_router/go_router.dart’;

Then we need to configure go router routes in our app like this:

final router = GoRouter(
initialLocation: '/login',
navigatorKey: rootNavigatorKey,
routes: [
GoRoute(
name: 'home',
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
name: 'login',
path: '/login',
builder: (context, state) => const LoginPage(),
),
GoRoute(
name: 'register',
path: '/register',
builder: (context, state) => const RegisterPage(),
),
]);

To use this configuration in our app first modify/replace MaterialApp with MaterialApp.router and the set route config parameter like this:

final rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: kDebugMode,
theme: ThemeData(
primaryColor: Colors.blue,
brightness: Brightness.light,
),
title: 'Go router',
routerConfig: router,
);
}
}

Now, we can use go router navigation anywhere in our project. Before navigating from one page to another page we need to understand routing methods: go and push.

Go router navigation package gives us a short syntax: context for routing.

context.go(String location, {Object? extra}); 
context.push(String location, {Object? extra});
context.goNamed( String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
});
context.pushNamed( String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
});

Let’s understand go and push concepts with examples:

go & goNamed: Thego and goNamed methods are declarative routing techniques that allow for direct navigation between pages without adding to the stack history. The goNamed method directly navigates from one page to another, while the goNamed method uses named parameters to do the same. Both methods allow for efficient navigation without the need to keep track of the stack history.

class LoginPage extends StatelessWidget {
const LoginPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Login Page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(onPressed: () {
//navigates to home page
context.go('/);
},
child: const Text('Login')),
ElevatedButton(
onPressed: () {
// navigates to register page
context.goNamed('register');
},
child: const Text('Go to register page'))
],
),
),
);
}
}
class RegisterPage extends StatelessWidget {
const RegisterPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Register Page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(onPressed: () {}, child: const Text('Register')),
ElevatedButton(
onPressed: () {
context.pop();
},
child: const Text('Back to login page'))
],
),
),
);
}
}
Exception has occurred.
GoError (GoError: There is nothing to pop)

In the above example, the Login page contains two buttons: a login button and a Go to register page button. The ‘login’ button pushes the Home page onto the stack using the context.go() method and removes the current screen (the Login page) from the stack. After jumping to the register page, If the user presses the 'back to login page' button, it will throw an exception: There is nothing to pop because the go method replaced the current screen on the stack with a new screen (the Home page).

fig: stack

push & pushNamed: The push and pushNamed methods are familiar navigation techniques that involve pushing a given route onto the stack. The push method pushes the route directly onto the stack, while the pushNamed the method uses a named parameter to do the same. Both methods allow for navigation while keeping track of the stack history.

class LoginPage extends StatelessWidget {
const LoginPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Login Page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(onPressed: () {
context.go('/);
}, child: const Text('Login')),
ElevatedButton(
onPressed: () {

context.push('/register');

},
child: const Text('Go to register page'))
],
),
),
);
}
}
class RegisterPage extends StatelessWidget {
const RegisterPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Register Page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(onPressed: () {}, child: const Text('Register')),
ElevatedButton(
onPressed: () {
context.pop();
},
child: const Text('Back to login page'))
],
),
),
);
}
}

In the above example, the Login page contains two buttons: one for logging in and one for navigating to the register page. The login button uses the context.push() method to push the Home page onto the stack, without removing the current screen (the Login page) from the stack. Thepush or pushNamedmethods store the routing history, allowing users to navigate back to previous screens if needed.

fig: stack

Error Handling:

By default, Go Router provides default error screens for both MaterialApp and CupertinoApp. If an invalid route is found, the default error screen will be displayed. However, you can use the errorBuilder parameter to replace the default error screen with a custom one. This allows you to customize the appearance and functionality of the error screen to better fit your app’s needs.

GoRouter(
initialLocation: '/login',
navigatorKey: rootNavigatorKey,
errorBuilder: (context,state)=>const ErrorPage()
/* ... */

)
class ErrorPage extends StatelessWidget {
const ErrorPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Error page')),
body: const Center(child: Text('No Route Found')));
}
}

Parameters

Using the go router package there are mainly three ways to pass parameters to the route.
1. params
2. queryParams
3. extra

params: If you want to pass a number of known parameters to your destination route, you can use the context.goNamed() or context.pushNamed() methods. Both of these methods have access to send parameters to your route

Define & Add params like this:

GoRoute(
name: 'detail',
path: '/detail/:id',
builder: (context, state) => DetailPage(
params: state.params['id']!,
),
);
// Home page
ElevatedButton(
onPressed: () {
context.pushNamed('detail', params: {'id': 'abc123'});
},
child: const Text('Params')),

Receive params like this:

class DetailPage extends StatelessWidget {
final String? id;

const DetailPage({
super.key,
this.id,
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Detail page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Params: $id"),
],
),
),
);
}
}

queryParams: If you want to pass query parameters to your destination route, you can use the context.goNamed() or context.pushNamed() methods. Both of these methods have access to send parameters to your route. One advantage of using queryParams this is because you do not have to explicitly define them in your route path, and they can be easily accessed using the state.queryParams method.

Define & Add query params like this:

  GoRoute(
name: 'detail',
path: '/detail',
builder: (context, state) => DetailPage(
queryParams: state.queryParams,
),
);
//Home page
ElevatedButton(
onPressed: () {
context.pushNamed('detail', queryParams: {
'category': 'test',
'price': '1000',
'color': 'red'
});
},
child: const Text('Query Params'));

Receive query params like this:

class DetailPage extends StatelessWidget {
final Map<String, String>? queryParams;
const DetailPage({super.key, this.queryParams});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Detail page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Query Params: $queryParams"),
],
),
),
);
}
}

extra: If you want to pass an object to your destination route, use the context.go(), context.goNamed(),context.push(), or context.pushNamed() methods. These methods have the ability to send parameters to your route.

Define & Add object like this:

  GoRoute(
name: 'detail',
path: '/detail',
builder: (context, state) => DetailPage(
detailPageParams: state.extra as DetailPageParams,
),
),
//Home page
ElevatedButton(
onPressed: () {
context.pushNamed('detail',
extra:
DetailPageParams(name: "Test test", number: 12345));
},
child: const Text('Extra')),

Receive object like this:

class DetailPageParams {
final String? name;
final int? number;
DetailPageParams({
this.name,
this.number,
});
}

class DetailPage extends StatelessWidget {
final DetailPageParams? detailPageParams;
const DetailPage({
super.key,
this.detailPageParams,
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Detail page"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Object: ${detailPageParams!.name} & ${detailPageParams!.number} ')
],
),
),
);
}
}

Thank You, Happy Coding.

References:

  1. https://docs.google.com/drawings
  2. https://pub.dev/packages/go_router

--

--