Ask the guards in a router
A module can keep the user from the rest of the app with a guard, which it declares as data of the router role. Guards of routes describes what the user then sees, and what the role remembers. This page is for a module that provides the router role: what it calls, and when.
The class of the role
In an app with guards, the template of the role generates routeGuards, redirectOf(), guardChanges and the class GuardedNavigation in lib/core/router/app_router.dart. The class keeps what the guards make a router remember, so a router only asks it and shows what it answers:
- The router creates one
GuardedNavigation<L>, whereLis how it knows a location that it can show asgo()does, such as a URI. It gives the class its location/, the screen that the app starts on, and a function from anAppLocationof the navigation to its own location. - Before it shows a location, the router calls
asked(route, location)with the full name of the route of the location, ornullfor a location that does not belong to a route of a module. It asks for the location the app starts on, for each location thatgo(),push()orreplace()is asked to show, and for each location that the platform gives it. When the answer is a location, the router shows it in place of the other, asgo()to it does, and completes such apush()withnullat once. - Each time
guardChangesnotifies, the router callschanged(pages)with the pages that the user can get back to, the one on top first: those of its root navigator and of its selected branch. Each page comes with the full name of its route ornull, with its location, and with whetherpush()showed it. A router that has no page yet calls it with no pages. When the answer is a location, the router shows it in place of its whole stack. When it isnull, the router leaves everything as it is.
With go_router
go_router knows a location by its URI. Its GoRouter asks in its top-level redirect, and push() asks before it hands the location to go_router:
final GuardedNavigation<String> _guards = GuardedNavigation(
start: '/',
locationOf: (location) => location.path,
);
redirect: (context, state) =>
_guards.asked(state.topRoute?.name, '${state.uri}')?.location,
Future<T?> push<T extends Object?>(AppLocation location) {
final guarded = _guards.asked(location.routeName, location.path);
if (guarded != null) {
config.go(guarded.location);
return Future.value();
}
return config.push<T>(location.path);
}
An app without guards has none of these names in app_router.dart, so a provider renders this code only when routerRole.facadeOf(input).guards is not empty. go_router gives its brick the variable guards for it, as its render hook shows.
What is checked
The contract harness checks that, in an app with guards, the files of the provider create a GuardedNavigation and read guardChanges. So a router that knows nothing of the guards cannot be in an app with a module that needs them.
Whether the router shows what the class answers, only a running app shows. SMF's CI checks that in running apps, with every provider of the router role that SMF has. It also runs the same tests on routers with a known bug, such as one whose replace() leaves the stack as it is, and each must fail on its bug. Contributing lists these tests.