DEV Community šŸ‘©ā€šŸ’»šŸ‘Øā€šŸ’»

Cover image for A beginnerā€™s guide to go_router in Flutter
CšŸ’™demagic
CšŸ’™demagic

Posted on

A beginnerā€™s guide to go_router in Flutter

> 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

Enter fullscreen mode Exit fullscreen mode

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",
);

Enter fullscreen mode Exit fullscreen mode

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(),
    )
  ],
);

Enter fullscreen mode Exit fullscreen mode

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"),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

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"),
),

Enter fullscreen mode Exit fullscreen mode

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"),
),

Enter fullscreen mode Exit fullscreen mode

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(),
        )
      ],
    ),
  ],
);

Enter fullscreen mode Exit fullscreen mode

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(),
                )
              ],
            )
          ],
        )
      ],
    )
  ],
);

Enter fullscreen mode Exit fullscreen mode

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"]!,
          ),
        )
      ],
    ),
  ],
);

Enter fullscreen mode Exit fullscreen mode

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"),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

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"]!,
          ),
        )
      ],
    ),
  ],
);

Enter fullscreen mode Exit fullscreen mode

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"),
),

Enter fullscreen mode Exit fullscreen mode

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"),
),

Enter fullscreen mode Exit fullscreen mode

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"]!,
   );
 },
)

Enter fullscreen mode Exit fullscreen mode

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"),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Now add thisĀ ErrorScreenĀ to theĀ errorBuilderĀ argument of theĀ GoRouterĀ object.

final GoRouter _router = GoRouter(
  errorBuilder: (context, state) => const ErrorScreen(),
);

Enter fullscreen mode Exit fullscreen mode

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(),
);

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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:

Sign up

  1. Create a new Codemagic project and connect your repository.

  1. SelectĀ FlutterĀ as the project type.

  1. UnderĀ Automatic build triggering, checkĀ Trigger on push.

  1. In theĀ BuildĀ tab, select your Flutter version, build format, and custom build arguments.

  1. Click on theĀ Start new buildĀ button to initiate the first build of your application.

  2. 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)

Create an Account!

šŸ‘€ Just want to lurk?

That's fine, you can still create an account and turn on features like šŸŒš dark mode.