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)