Manage App Navigation
Routing is implemented with go_router and a thin RouterService wrapper so navigation can still be triggered from ViewModels without passing BuildContext around.
This gives you URL synchronization and deep linking on web, while keeping navigation logic simple and testable.
Default Routes
The boilerplate comes with two routes which should always exist:
/- The home page./404- The 404 page, when a route is not found.
final routes = [ GoRoute( path: RoutePaths.home, pageBuilder: (context, state) => _buildPage(const HomeView(), state), ), GoRoute( path: RoutePaths.notFound, pageBuilder: (context, state) => _buildPage(const NotFoundView(), state), ),];Adding a New Route
Add new routes using GoRoute.
GoRoute( path: '/new-route', pageBuilder: (context, state) => _buildPage(const NewRouteView(), state),),Dynamic route params use : in the path.
GoRoute( path: '/new-route/:id', pageBuilder: (context, state) { final id = state.pathParameters['id']; return _buildPage(NewRouteView(id: id), state); },),Use nested routes for parent-child relationships. Child routes should use relative paths (no leading /).
Using a Route
RouterService exposes go_router-aligned methods only.
go
Default navigation method. Updates the URL and rebuilds stack from route hierarchy.
locator<RouterService>().go('/new-route');push
Pushes a temporary page/flow on top of the stack.
locator<RouterService>().push('/new-route');replace
To replace the current route, you can use the replace method.
locator<RouterService>().replace('/new-route');canPop and pop
Back navigation checks are available through canPop().
if (locator<RouterService>().canPop()) { locator<RouterService>().pop();}Passing Data to a Route
There are 3 common ways to pass data.
Path Parameters
As shown above, dynamic segments become pathParameters.
// configure the route in route_config.dartGoRoute( path: '/todos/:id', pageBuilder: (context, state) { final id = state.pathParameters['id']; return _buildPage(TodoView(id: id), state); },),
// go to the routelocator<RouterService>().go('/todos/123');Query Parameters
You can also pass data in the URL query string.
// configure the route in route_config.dartGoRoute( path: '/todos', pageBuilder: (context, state) { final id = state.uri.queryParameters['id']; return _buildPage(TodoView(id: id), state); },),
// go to the routelocator<RouterService>().go('/todos?id=123');Extra
For non-URL data, pass a typed object using extra.
// configure the route in route_config.dartGoRoute( path: '/todos', pageBuilder: (context, state) { final todo = state.extra as Todo; return _buildPage(TodoView(id: todo.id, name: todo.name), state); },),
// go to the routelocator<RouterService>().go('/todos', extra: Todo(id: '123', name: 'Test'));Automatic Routing Depending on Auth
For auth-based navigation, keep guard logic in router configuration instead of scattering checks across ViewModels.
Pass auth-aware routing hooks into RouterService:
redirectfor auth guard decisions (for example login-gated routes)refreshListenableso auth state changes trigger redirect re-evaluation
This keeps auth routing centralized while still using RouterService methods (go, push, replace, pop) for normal app navigation.
Unknown routes are redirected to /404 through the router exception handler with a loop guard, so you get a stable not-found flow without manual stack management.