Navigation topic

There are many ways to navigate between destinations in your app.

Go directly to a destination

Navigating to a destination in GoRouter will replace the current stack of screens with the screens configured to be displayed for the destination route. To change to a new screen, call context.go() with a URL:

build(BuildContext context) {
  return TextButton(
    onPressed: () => context.go('/users/123'),
  );
}

This is shorthand for calling GoRouter.of(context).go('/users/123).

To build a URI with query parameters, you can use the Uri class from the Dart standard library:

context.go(Uri(path: '/users/123', queryParameters: {'filter': 'abc'}).toString());

Imperative navigation

GoRouter can push a screen onto the Navigator's history stack using context.push(), and can pop the current screen via context.pop(). However, imperative navigation is known to cause issues with the browser history.

To learn more, see issue #99112.

You can use a Link widget from the url_launcher package to create a link to destinations in your app. This is equivalent to calling context.go(), but renders a real link on the web.

To add a Link to your app, follow the Link API documentation from the url_launcher package.

Using named routes

You can also use Named routes to navigate instead of using URLs.

Prevent navigation

GoRouter and other Router-based APIs are not compatible with the WillPopScope widget.

See issue #102408 for details on what such an API might look like in go_router.

Disable browser history tracking when navigating

To disable browser history tracking when navigating, use the neglect method of the Router class:

ElevatedButton(
  onPressed: () => Router.neglect(
    context,
    () => context.go('/destination'),
  ),
  child: ...
),

To disable browser history tracking for the entire application, set the routerNeglect property of the GoRouter widget to true:

final _router = GoRouter(
  routerNeglect: true,
  routes: [
    ...
  ],
);

Imperative navigation with Navigator

You can continue using the Navigator to push and pop pages. Pages displayed in this way are not deep-linkable and will be replaced if any parent page that is associated with a GoRoute is removed, for example when a new call to go() occurs.

To push a screen using the imperative Navigator API, call NavigatorState.push():

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (BuildContext context) {
      return const DetailsScreen();
    },
  ),
);

The behavior may change depends on the shell route in current screen and the new screen.

If pushing a new screen without any shell route onto the current screen with shell route, the new screen is placed entirely on top of the current screen.

An animation shows a new screen push on top of current screen

If pushing a new screen with the same shell route as the current screen, the new screen is placed inside of the shell.

An animation shows pushing a new screen with the same shell as current screen

If pushing a new screen with the different shell route as the current screen, the new screen along with the shell is placed entirely on top of the current screen.

An animation shows pushing a new screen with the different shell as current screen

To try out the behavior yourself, see push_with_shell_route.dart.

Returning values

Waiting for a value to be returned:

onTap: () {
  final bool? result = await context.push<bool>('/page2');
  if(result ?? false)...
}

Returning a value:

onTap: () => context.pop(true)

Using extra

You can provide additional data along with navigation.

context.go('/123', extra: 'abc');

and retrieve the data from GoRouterState

final String extraString = GoRouterState.of(context).extra! as String;

The extra data will go through serialization when it is stored in the browser. If you plan to use complex data as extra, consider also providing a codec to GoRouter so that it won't get dropped during serialization.

For an example on how to use complex data in extra with a codec, see extra_codec.dart.