> Written by Hrishikesh Pathak and originally published to Codemagic blog.
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.
go_Router
Ā is 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.
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.
Top comments (0)