With go_router, what is the difference between context.go and context.push, and when would you use each?
answer
- declarative versus imperative
- go rebuilds the stack from the route tree
- push stacks on top, returns a Future
- pushed pages leave the web URL alone
- a later go discards pushed pages
basics
~20 scontext.go replaces the whole page stack with the pages the destination's route hierarchy defines and updates the URL; context.push adds the matched page on top of the current stack and returns a Future that completes with the value passed to pop.
solid answer
~40 s`context.go('/accounts/42')` is declarative: go_router matches the location, builds the page stack from the route tree (the parent `/accounts` page under the `:accountId` child) and throws away whatever was there, including pages pushed earlier. It returns `void`. `context.push('/accounts/42')` is imperative: it adds the matched page on top of the current stack and returns a `Future<T?>` that completes with the result of `context.pop(result)`. Since go_router 8, pushed pages do not change the browser URL by default, which keeps reflecting the declarative stack underneath, and they are not deep-linkable. So I use `go` for moves between destinations a URL should describe, and `push` for a flow that returns a value, such as a confirmation page.
code
dart · 31 linesimport 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class AccountActions extends StatelessWidget {
const AccountActions({super.key, required this.accountId});
final String accountId;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextButton(
// A destination: the stack becomes /accounts under /accounts/:accountId.
onPressed: () => context.go('/accounts/$accountId'),
child: const Text('Open account'),
),
TextButton(
onPressed: () async {
// A step that answers: stacked on top, awaited for the pop result.
final bool? confirmed = await context.push<bool>('/transfers/confirm');
if (!context.mounted) return;
if (confirmed ?? false) {
context.go('/accounts/$accountId');
}
},
child: const Text('Transfer'),
),
],
);
}
}go deeper
Recall that go replaces the stack with what the route tree defines for a location, while push adds a page on top and can be awaited for a pop result.
Explain how go builds parent pages from nested GoRoutes, why pushed pages leave the web URL alone since go_router 8, and how replace differs from pushReplacement.
Show you keep every bookmarkable destination reachable through go so deep links and taps produce the same stack, reserving push for value-returning flows.
Weigh a URL-first navigation design against imperative convenience, and argue when flags like optionURLReflectsImperativeAPIs cost more than they save.
## Two navigation models on one router go_router sits on Flutter's Router API, where the page stack is derived from a **configuration**, not built up call by call. It still offers imperative calls for the cases where a stack-on-top page is natural. The `BuildContext` extension methods `context.go` and `context.push` are shorthand for `GoRouter.of(context).go` and `.push`. ## go: navigate to a location `go(String location, {Object? extra})` hands the location to the route information provider, which parses it into a match list. The resulting stack is **whatever the route tree defines** for that location: - With `GoRoute(path: '/accounts', routes: [GoRoute(path: ':accountId', ...)])`, `go('/accounts/42')` produces the accounts list page with the account detail page on top, so the AppBar back button returns to the list even when the user arrived by a deep link. - The previous stack is **replaced**, including any pages added with `push`. - The URL (on the web, the address bar) becomes the location. - The method returns `void`; there is nothing to await. ## push: stack a page on top `push<T>(String location, {Object? extra})` matches the location and adds it as an **imperative match** on top of the current configuration. It returns `Future<T?>`, completed when that page is popped: ```dart final bool? confirmed = await context.push<bool>('/transfers/confirm'); // on the confirm page: context.pop(true); ``` Consequences worth naming: - Since go_router 8.0, **imperatively pushed routes no longer change the URL**; the address bar keeps showing the declarative location beneath, and browser back and forward still respect the pushed pages. - `GoRouter.optionURLReflectsImperativeAPIs`, a static flag that defaults to `false`, restores the old behaviour, but the package documentation recommends against it because the top page's URL is not always deep-linkable. - A pushed page is lost when a later `go` rebuilds the stack. ## Side by side | | `context.go` | `context.push` | |---|---|---| | Stack effect | replaced by the route tree for the location | page added on top | | Returns | `void` | `Future<T?>` with the pop result | | Web URL | becomes the location | unchanged by default | | Deep-linkable | yes | no | | Survives a later `go` | not applicable | no | ## The replacement variants - `pushReplacement(location)` replaces the top page with a **new page key**, so the replaced page is disposed and a transition animation runs. - `replace(location)` replaces the top page but **reuses its page key**, preserving state and skipping the page animation. - `goNamed`, `pushNamed`, `pushReplacementNamed` and `replaceNamed` do the same by route `name` with `pathParameters` and `queryParameters` maps. ## Pitfalls that show up in review - **Switching sections with `push`.** Tapping between accounts, cards and settings with `push` grows an ever-deeper stack and a back button that walks through every past section; destinations should be reached with `go`. - **Expecting a result from `go`.** It returns `void`; code that needs an answer must `push` and await. - **Popping the last page.** `context.pop()` on the only page has nothing beneath it; `context.canPop()` answers whether there is something to return to, which matters on screens that may be opened directly by a deep link. - **Popping a dialog by accident.** `GoRouter.pop` removes the top-most route, and if that is a dialog or bottom sheet it closes that instead of the page under it. - **Relying on push order for structure.** A detail page that assumes the list page is beneath it because the list pushed it breaks when a link opens the detail directly; nest the detail route under the list so `go` builds the same stack. ## Choosing, in a banking app 1. Moving between destinations the user could bookmark or share (accounts list, one account, card settings): `go`. 2. A step that returns an answer to the caller (confirm a transfer, pick a payee): `push` and await the result. 3. Replacing a step in a wizard without leaving it on the stack: `pushReplacement`, or `replace` when the page should keep its state. Mixing them thoughtlessly produces the classic bug: the app works when tapped through with `push`, but a deep link to the same location builds a different, shallower stack, because only the route tree, not the sequence of pushes, defines what `go` shows.
- With go_router, what does GoRouter.optionURLReflectsImperativeAPIs change?It is a static flag, `false` by default, that affects only the web. Set to `true`, the address bar shows the top-most `GoRoute` including pages added with `push`. It exists for backward compatibility, and the package advises against it because the top page's URL is not always deep-linkable.
- With go_router, how does replace differ from pushReplacement?Both swap the top page for the given location. `pushReplacement` gives the new page a new page key, so the old page is disposed and a transition runs; `replace` reuses the page key, so state is preserved and no page animation plays.
- With go_router, what happens to a pushed transfer-confirmation page when code calls context.go('/accounts')?It disappears. `go` rebuilds the stack from the route tree for `/accounts`, and imperative matches added by `push` are not part of that tree, so the confirmation page and everything else above the new stack are discarded.
go is handing a taxi driver a full address: the route to it is worked out from the map, whatever streets you were on before. push is stepping into the shop next door: you come back out to exactly where you stood, carrying whatever you bought.
saying these in an interview costs you the question
- Says context.go pushes the new page onto the existing stack.
- Awaits context.go for a result, though go returns void.
- Believes a pushed route always updates the browser address bar.
- Uses push for every move, so deep links produce a different stack.
- Treats replace and pushReplacement as exact aliases.