Written by Hrishikesh Pathak
Routing is a crucial aspect of an app. Just like when you manage the application state or build the UI, you should give sufficient attention to optimizing the routing of your application. An optimized routing system helps users navigate your app and can handle the user state efficiently.
In Flutter, there are several approaches to routing. One of them is go_Router
– a declarative and minimal routing system built on top of Flutter’s Router API. go_router
provides a convenient URL-based API to navigate between different screens.
In this article, we’ll help you learn how to use go_router
in Flutter. We will discuss how to use route parameters, navigate using named routes, handle 404 errors, and much more. To get the most out of this article, be sure to read it to the end.
Please follow this GitHub link for the source code to follow along.
Adding go_router to your Flutter project
Open a terminal in your Flutter project. Then run the following command to install the go_router package in your Flutter project.
flutter pub add go_router
This command installs the latest version of go_router
in your project. If you want to install a different version, just replace the version number in the pubspec.yaml
file, and run flutter pub get
to get your desired version.
Setting up basic routing using go_router
To integrate go_router
in your app, change your MaterialApp
widget to MaterialApp.router
. This constructor accepts a routerConfig
property.
MaterialApp.router(
routerConfig: _router,
title: "Go router",
);
Next, we’ll add a GoRouter
object _router
to the routerConfig
property. Let’s configure our GoRouter
and add the home and settings routes.
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: "/",
builder: (context, state) => const HomePage(),
),
GoRoute(
path: "/settings",
builder: (context, state) => const SettingsPage(),
)
],
);
When someone visits the /
route, GoRouter
returns the HomePage
widget. Similarly, if someone visits the /settings
route, GoRouter
returns the SettingsPage
widget.
Let’s see what HomePage
and SettingsPage
look like in the code.
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Homepage"),
),
body: Center(
child: ElevatedButton(
),
),
);
}
}
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text("Settings"),
),
body: const Center(
child: Text("Settings Page"),
),
);
}
}
How to navigate between routes with go_router
To navigate between routes, we can use the GoRouter.of(context).go()
method or the more concise context.go()
method. Let’s add this method in the ElevatedButton
in our HomePage
widget to navigate to the /settings
route.
child: ElevatedButton(
onPressed: () => context.go("/settings"),
child: const Text("Go to Settings page"),
),
Similarly, add an ElevatedButton
in the SettingsPage
to come back to the home /
route.
child: ElevatedButton(
onPressed: () => context.go("/"),
child: const Text("Go to home page"),
),
Subroutes in go_router
In the above section, we define our GoRouter
object. There, you can observe that we put a leading /
in every route. If you need deeply nested routes, then you have to type the whole route with the leading /
every time. This also makes the routes less organized.
GoRouter provides a routes
argument in every GoRoute
object to group subroutes and define nested routes more easily.
If we convert our previous GoRouter
object to a subroutes structure, then the new GoRouter
should look like this.
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: "/",
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: "settings",
builder: (context, state) => const SettingsPage(),
)
],
),
],
);
Can you see the difference? Now, the settings
routes live inside the /
routes. Therefore, there’s no need to specify a leading /
in subroutes.
Let’s look at another example so that we can grasp the concept completely. If you want to define the nested route /a/b/c
, then the GoRouter object should look like this.
final GoRouter _newRouter = GoRouter(
routes: [
GoRoute(
path: "/",
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: "a",
builder: (context, state) => const PageA(),
routes: [
GoRoute(
path: "b",
builder: (context, state) => PageB(),
routes: [
GoRoute(
path: "c",
builder: (context, state) => PageC(),
)
],
)
],
)
],
)
],
);
Adding route parameters in go_router
It is very easy to add route parameters in go_router
. To define a route parameter, add a trailing :
with the parameter name in the path
argument of GoRoute
.
For example, if you want to add a name
parameter in the settings route, the path
argument should be /settings:name
. You can access the route parameter with the state.params["name"]
variable.
Let’s see how to implement this in our Flutter app. The modified GoRouter
with a route parameter in the settings route should look like this.
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: "/",
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: "settings/:name",
builder: (context, state) => SettingsPage(
name: state.params["name"]!,
),
)
],
),
],
);
To receive the route parameter in the settings screen, let’s modify our code and add a property called name
.
class SettingsPage extends StatelessWidget {
final String name;
const SettingsPage({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text(name),
),
body: Center(
child: ElevatedButton(
onPressed: () => context.go("/"),
child: const Text("Go to home page"),
),
),
);
}
}
Now, when we visit /settings/codemagic
, the codemagic
route parameter is passed to the SettingsPage
and displays the variable on the screen. Similarly, if you visit the /settings/hrishikesh
route, hrishikesh
is displayed in the SettingsPage
.
Named routes in go_router
Writing route paths manually is cumbersome and error prone. Therefore, go_router
offers a named route feature to navigate around the app, and go_router
will automatically resolve the path itself.
To generate named routes, let’s change our GoRouter
configuration with the name parameter.
final GoRouter _router = GoRouter(
routes: [
GoRoute(
name: "home",
path: "/",
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
name: "settings",
path: "settings/:name",
builder: (context, state) => SettingsPage(
name: state.params["name"]!,
),
)
],
),
],
);
Passing route parameters to a named route is different from what you normally see. You have to define a params
parameter in the context.goNamed()
function. Here’s how to set up navigation to SettingsPage
from HomePage
with a route parameter.
child: ElevatedButton(
onPressed: () => context.goNamed(
"settings",
params: {"name": "codemagic"},
),
child: const Text("Go to Settings page"),
),
Passing query parameters in go_router
You have access to queryParams
in the context.goNamed()
function. The best thing about queryParams
is that you don’t have to explicitly define them in your route path and can easily access them using the state.queryParams
method. You can add miscellaneous user-related data as a query parameter.
Let’s look at an example to illustrate this concept. In the ElevatedButton
in HomePage
, let’s add some query parameters to the settings route.
child: ElevatedButton(
onPressed: () => context.goNamed("settings", params: {
"name": "codemagic"
}, queryParams: {
"email": "example@gmail.com",
"age": "25",
"place": "India"
}),
child: const Text("Go to Settings page"),
),
Now we can access these query parameters inside the GoRouter
configurations and pass them to the page. In this example, I am just printing out the values for demonstration.
GoRoute(
name: "settings",
path: "settings/:name",
builder: (context, state) {
state.queryParams.forEach(
(key, value) {
print("$key:$value");
},
);
return SettingsPage(
name: state.params["name"]!,
);
},
)
Now, when you click the button to navigate to the SettingsPage
, you can see the following output on the console screen.
Handling 404 errors with go_router
When a user visits a screen that is not defined in the GoRouter
configuration, it causes an exception. But go_router
provides a very easy way to handle these errors, and you can provide a custom page to show to the user when this type of exception occurs.
We can define an errorBuilder
argument in the GoRouter
object to handle these 404 errors. Let’s make an ErrorScreen
page that includes a button to navigate back to the HomePage
.
class ErrorScreen extends StatelessWidget {
const ErrorScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text("Error Screen"),
),
body: Center(
child: ElevatedButton(
onPressed: () => context.go("/"),
child: const Text("Go to home page"),
),
),
);
}
}
Now add this ErrorScreen
to the errorBuilder
argument of the GoRouter
object.
final GoRouter _router = GoRouter(
errorBuilder: (context, state) => const ErrorScreen(),
);
That’s it. Try to launch your Flutter app in the Chrome browser and navigate to a random path. You will see that ErrorScreen
will appear as a response.
Redirecting routes using go_router
Sometimes, you want to programmatically navigate your user from one page to another based on certain logic. You can enable that feature using redirects in go_router
. You can define a redirect for every path and a global redirect for all pages.
For example, when a new user opens your application, you can navigate them to the login screen instead of the home screen. Similarly, when a logged-in user opens your application, you can navigate them to the home page. You can implement this type of feature very easily using go_router
.
Let’s see how to implement a redirect in your app. Inside the route, define a redirect
argument. Inside the redirect
builder, check if the user is logged in or not. If they are not logged in, navigate them to the /login
page. Otherwise, show them the home page.
final GoRouter _router = GoRouter(
routes: [
GoRoute(
name: "home",
path: "/",
builder: (context, state) => const HomePage(),
redirect: (context, state) {
if (userIsNotLoggedIn){
return "/login"
}
return "/"
},
),
],
errorBuilder: (context, state) => const ErrorScreen(),
);
GoRouter navigation comparison: go vs. push
GoRouter has two methods for navigating between pages: GoRouter.of(context).go()
and GoRouter.of(context).push()
. On the surface, they look very similar, but they function differently. The push()
method stacks one page on another in navigation, while the go()
method directly navigates from one path to another without any stacking.
Here’s an example to demonstrate this concept. Assume you have three pages: A, B, and C. If you navigate from A to B to C using the push
method, then C is stacked on top of B on top of A. Take a look at this diagram to understand better.
On the other hand, if you navigate from A to B to C using the go
method, then C is only stacked on top of A and only if A is the home page with the route /
. B is eliminated from the navigation stack altogether. Therefore, the go()
method leaves no routing history.
You can choose which strategy works best for your apps.
Bonus: Remove the # prefix from the web URL in Flutter
If you launch your app on the web, you can see a trailing #
in the URL. Sometimes it looks very annoying, and you may want to remove it. You can easily achieve this with the help of a package called url_strategy.
First, install this package in your Flutter project.
flutter pub add url_strategy
Then import this package in your main.dart
file, and add the setPathUrlStrategy()
function before the runApp()
function.
Now, run your application. You can see that the trailing #
is removed from the URL of your Flutter app.
Choosing between go_router, Navigator 2.0, and Beamer for navigation in Flutter
Flutter has several packages you can use for routing. The most well-known ones are go_router, Navigator, and Beamer. How do you choose which navigation to use for your app?
If you are primarily targeting mobile platforms (Android and iOS) and don’t have time to learn a new package for routing, it’s completely fine to use Navigator 1.0. It is capable of meeting all your routing needs in Android/iOS apps.
Flutter’s core strength is in its multiplatform support, which spans beyond just Android and iOS. If you want to make a real multiplatform app that supports desktop and web platforms in addition to Android/iOS, then you should migrate to Navigator 2.0. Navigator 2.0 provides a very flexible and fully customizable routing solution for truly multiplatform apps.
But a huge chunk of developers don’t like the verbose nature of Navigator 2.0, which usually requires you to write boilerplate code to create a simple route. If you want to avoid these issues, you can use go_router, which is declarative and a significantly simpler solution for Flutter. If you have a medium-sized to large application and want to handle dynamic routes, go_router
can help you minimize your effort and maximize the results.
Beamer is the new kid on the Flutter navigation stack. Like go_router
, Beamer is also based on the Flutter router API. It makes developers’ lives easier by simplifying Navigator 2.0. If you are already using go_router
, then there is no need to switch to Beamer unless you are missing some features and Beamer can satisfy your needs.
If you are a beginner Flutter dev, you can go with either go_router
or Beamer. Both are good and focus on developer productivity by removing complexity and boilerplate code.
Build your Flutter app using Codemagic
Our app isn’t completely ready for release at this point, but let’s set up a CI/CD pipeline for it anyway. Ideally, you should do this right from the start of the development process to make it easier to collaborate on the app with other developers and deliver new features to testers and customers while avoiding falling into the “it works on my computer” trap.
Codemagic is a CI/CD platform for Flutter applications. You can trigger a new Flutter build when you push your code to your GitHub/GitLab/Bitbucket repository. Then just download the generated artifacts from the Codemagic dashboard and run them on your devices (unless you want to publish them to stores, which Codemagic can also help you with). Let’s see step by step how to set up a CI/CD pipeline with Codemagic for your Flutter application.
In case you are not a Codemagic user yet, you can sign up here:
- Create a new Codemagic project and connect your repository.
- Select Flutter as the project type.
- Under Automatic build triggering, check Trigger on push.
- In the Build tab, select your Flutter version, build format, and custom build arguments.
Click on the Start new build button to initiate the first build of your application.
After the build completes, you can download the artifacts and run the app on your devices.
Conclusion
go_router
is a very minimalistic but powerful routing package for Flutter. It removes all the complexity that Flutter Navigator 2.0 brings to the table and provides a very developer-friendly API to work with.
In this article, we have discussed how to use go_router
in your Flutter app as well as some of its features, like route parameters, query parameters, named routes, handling errors, and redirection. This is just a beginner’s guide for getting started with go_router
. If you want to learn more, visit the official go_router
API documentation.
If you have any questions, you can ask me on Twitter. Have a good day.
You can find the sample app on GitHub.
Hrishikesh Pathak is a Flutter developer and writer. He likes to explore new technology and write about it. Check out his posts on Codemagic’s blog and his personal blog.