navigation-and-routing
Enforces one app-wide GoRouter in lib/routing/ wired via MaterialApp.router, deep-linkable identity in path params never state.extra, context.go-vs-context.push discipline, redirect guards as pure functions driven by a Riverpod refreshListenable, StatefulShellRoute.indexedStack for branch-state-preserving BottomNavigationBar/NavigationRail shells, CustomTransitionPage transitions that respect reduced motion, PopScope (canPop/onPopInvokedWithResult) for unsaved-changes interception, and an errorBuilder 404 route. Use when adding routes, GoRoute, redirect, auth/onboarding gates, deep links, ShellRoute or nested navigation, bottom-nav/rail tab shells, page transitions, back-button/unsaved-changes handling, notification-payload-to-location mapping, typed routes, go_router_builder, or wiring go_router into app.dart.
How do I install this agent skill?
npx skills add https://github.com/zakariaf/flutter-skills --skill navigation-and-routingIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The navigation-and-routing skill provides architectural guidance and examples for implementing app-wide navigation using the go_router package and Riverpod state management in Flutter applications. It emphasizes secure practices such as using path parameters for deep-linking identity to ensure reliability after process death and provides static analysis scripts to verify these rules. No malicious code, exfiltration patterns, or obfuscation were detected.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
Navigation and routing
This skill owns app navigation with go_router. There is exactly ONE GoRouter for the app, defined in lib/routing/, and every screen is reachable by a URL. Navigation is a data structure (routes + a pure redirect), not a pile of imperative Navigator.push calls.
Read the reference for the task at hand:
references/go-router-config.md— the single router,context.govscontext.push, path params vsstate.extra, typed route helpers,errorBuilder/404.references/guards-and-redirects.md— pureredirectfunctions, the RiverpodrefreshListenable, auth + onboarding gates, avoiding redirect loops.references/shells-and-deep-links.md—StatefulShellRoute.indexedStackfor bottom-nav/rail shells,CustomTransitionPage,PopScope, and notification-payload → location mapping.
Run scripts/check_routing.sh before a PR.
Non-negotiable rules
- Exactly ONE
GoRouter, built inlib/routing/, wired once viaMaterialApp.routerinapp.dart. Multiple routers fragment history, deep links, and back-button behaviour. The router is created behind a provider so guards can watch app state. - Deep-linkable identity lives in PATH PARAMS, never in
state.extra.extrais a live Dart object: it isnullon a cold start from a deep link and after process death / restoration. A screen that needs an id to rebuild must read it fromstate.pathParametersso the URL alone fully reconstructs the screen. state.extrais ONLY an optional non-identity optimisation (a pre-fetched object to avoid a reload flash). The screen must still work — refetch by id — whenextraisnull.- Use
context.goto replace the stack (declarative destinations, tabs, post-login home); usecontext.pushto stack a screen you expect to pop back from (a detail, a modal flow). Mixing them wrong breaks the back button. Know which one every call site needs. - Guards are PURE
redirectfunctions.redirectreturns a new locationString?(ornullto allow) fromGoRouterState+ a snapshot of app state. No I/O, no navigation calls, no side effects insideredirect— it runs on every navigation and can run repeatedly. - Reactive guards use a
refreshListenable, not polling. Bridge the Riverpod auth/onboarding provider to aListenable; the router re-evaluatesredirectwhenever it fires. Seestate-management-riverpod. - Redirects must be loop-free. Always allow the destination the guard sends you TO (e.g. never redirect
/sign-inback to/sign-in). Guard againstA→B→Aby checking the current location before redirecting. - Tab/branch shells use
StatefulShellRoute.indexedStack. It preserves each branch's navigation stack and state across tab switches; a plainShellRoutewith anIndexedStackyou wire by hand does not survive router rebuilds as cleanly. Switch branches withnavigationShell.goBranch(index). - Custom transitions go through
CustomTransitionPageand respect reduced motion. When the platform requests reduced motion, collapse to a no-op/fade. Read the flag fromMediaQuery, resolve motion via the design system — seeaccessibility-as-codeanddesign-system-structure. - Intercept back / unsaved changes with
PopScope, notWillPopScope(removed). SetcanPop: falseand handle inonPopInvokedWithResult(bool didPop, T? result); only navigate away after the user confirms. - Provide an
errorBuilderand a real 404/error route. An unmatched deep link must land on a designed error screen, never a red error box. - Never hold a
BuildContextacross anawaitbefore navigating. CaptureGoRouter.of(context)(or the router) before the await, or guard withcontext.mountedafter. Seeasync-safety. - A feature does not build its own router.
scaffold-feature-moduleregisters a feature'sGoRouteINTO this router's route list; features never instantiateGoRouter.
The single router
app.dart reads the router from a provider and hands it to MaterialApp.router. The router itself lives in routing/ and is the only place GoRouter(...) is constructed.
// routing/app_router.dart
final routerProvider = Provider<GoRouter>((ref) {
// Bridge Riverpod auth state to a Listenable the router can watch.
final refresh = ValueNotifier<int>(0);
ref.onDispose(refresh.dispose);
ref.listen(authNotifierProvider, (_, __) => refresh.value++);
return GoRouter(
initialLocation: Routes.home,
refreshListenable: refresh,
redirect: (context, state) => appRedirect(ref.read(authNotifierProvider), state),
errorBuilder: (context, state) => ErrorScreen(error: state.error),
routes: $appRoutes, // assembled from feature route lists
);
});
// app.dart — the only MaterialApp.router
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
routerConfig: router,
theme: lightTheme,
darkTheme: darkTheme,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
);
}
}
Identity in the path, not in extra
// GOOD: id is in the URL — a cold-start deep link to /items/42 fully rebuilds.
GoRoute(
path: '/items/:id',
builder: (context, state) {
final id = state.pathParameters['id']!; // always present
// extra is an OPTIONAL fast-path; screen must work when it is null.
final preloaded = state.extra as Item?;
return ItemScreen(itemId: id, preloaded: preloaded);
},
),
// BAD: identity smuggled through extra — null on cold start / after process death.
context.push('/item', extra: item); // no id in the URL => not deep-linkable
Typed route helpers (constants, not string soup)
Prefer small constant/builder classes so call sites never hand-concatenate paths. go_router_builder codegen is an OPTIONAL upgrade, not the default.
// routing/routes.dart
abstract final class Routes {
static const home = '/';
static const items = '/items';
static String item(String id) => '/items/$id';
static const signIn = '/sign-in';
}
// call site
context.push(Routes.item(item.id));
go vs push
context.go(Routes.home); // replace whole stack: post-login, tab roots
context.push(Routes.item(id)); // stack a detail you'll pop back from
context.pop(result); // return up, optionally with a result
Anti-patterns
- Two
GoRouterinstances, or a nestedNavigator/MaterialAppinside a screen for "sub-navigation." Use nested routes /StatefulShellRoute. - Passing a domain id through
state.extraand readingextra!inbuild— crashes on cold start. - I/O,
ref.readof async work, or callingcontext.goINSIDEredirect. Redirect is pure and returns a location. - A
redirectthat can bounce forever because it also redirects its own target. - Mixing
Navigator.pushNamed('/x')string routes with go_router — one navigation system only. WillPopScope(removed) instead ofPopScope.- Awaiting then using the same
contextto navigate without amountedcheck. - A feature package/folder constructing its own
GoRouter.
Definition of done
- One
GoRouterinlib/routing/, oneMaterialApp.routerinapp.dart. - Every screen reachable by a URL; every id-bearing screen reads its id from
state.pathParameters. state.extrais only ever an optional optimisation; every such screen renders correctly withextra == null.- Guards are pure
redirectfunctions with arefreshListenable; no redirect loops. - Tab shells use
StatefulShellRoute.indexedStack; branch state survives tab switches. - Transitions respect reduced motion;
errorBuilder+ 404 route present. PopScopeguards unsaved changes; noBuildContextused across an await when navigating.scripts/check_routing.shpasses.
Related skills
app-startup-and-bootstrap— ownsmain()/bootstrap()ordering and whereMaterialApp.routeris mounted.state-management-riverpod— the auth/onboarding providers therefreshListenablebridges.scaffold-feature-module— registers a featureGoRouteinto this router.adaptive-layout— choosesNavigationRailvsBottomNavigationBarby width for the shell.accessibility-as-code— reduced-motion flag and semantics for navigation.design-system-structure— the reduced-motion token /resolveMotionhelper for transitions.async-safety—BuildContext/mountedrules around awaited navigation.local-notifications-scheduler— the notification payload whose pure mapper produces a location.
References
- go_router package: https://pub.dev/packages/go_router
- go_router API docs: https://pub.dev/documentation/go_router/latest/
- Flutter navigation & routing: https://docs.flutter.dev/ui/navigation
- Deep linking: https://docs.flutter.dev/ui/navigation/deep-linking
- PopScope API: https://api.flutter.dev/flutter/widgets/PopScope-class.html
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/zakariaf/flutter-skills/navigation-and-routing">View navigation-and-routing on skillZs</a>