Install
openclaw skills install go-routerDeclarative URL-based routing for Flutter using the Router API. Use when implementing navigation between screens, deep links, authentication guards, parameterized routes, shell routes with nested navigation, and web routing in Flutter apps.
openclaw skills install go-routerGoRouter is a declarative routing package for Flutter that uses the Router API to provide convenient URL-based navigation.
Define routes as a list of GoRoute objects:
final GoRouter _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/user/:id',
builder: (context, state) {
final userId = state.pathParameters['id']!;
return UserScreen(id: userId);
},
),
],
);
// Simple navigation
context.go('/details');
// With parameters
context.go('/user/123');
// Named navigation (requires name parameter)
context.goNamed('user', pathParameters: {'id': '123'});
// Push (adds to stack)
context.push('/modal');
// Replace current route
context.replace('/new');
// Go back
context.pop();
// With query parameters
context.go('/search?q=flutter');
Path parameters use : prefix:
GoRoute(
path: '/user/:id/posts/:postId',
builder: (context, state) {
final userId = state.pathParameters['id']!;
final postId = state.pathParameters['postId']!;
return PostScreen(userId: userId, postId: postId);
},
)
Query parameters are accessed via state.uri.queryParameters:
GoRoute(
path: '/search',
builder: (context, state) {
final query = state.uri.queryParameters['q'] ?? '';
return SearchScreen(query: query);
},
)
final routerProvider = Provider<GoRouter>((ref) {
final auth = ref.watch(authProvider);
return GoRouter(
initialLocation: '/',
redirect: (context, state) {
final isLoggedIn = auth.isLoggedIn;
final isAuthRoute = state.matchedLocation == '/login';
if (!isLoggedIn && !isAuthRoute) {
return '/login';
}
if (isLoggedIn && isAuthRoute) {
return '/';
}
return null;
},
routes: [
GoRoute(path: '/login', builder: (_, __) => const LoginScreen()),
GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
],
);
});
GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) {
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(
path: '/',
builder: (_, __) => const HomeScreen(),
),
GoRoute(
path: '/settings',
builder: (_, __) => const SettingsScreen(),
),
],
),
],
)
StatefulShellRoute(
branches: [
StatefulShellBranch(
routes: [GoRoute(path: '/feed', builder: (_, __) => FeedScreen())],
),
StatefulShellBranch(
routes: [GoRoute(path: '/profile', builder: (_, __) => ProfileScreen())],
),
],
)
GoRoute(
path: '/details',
pageBuilder: (context, state) => const MaterialPage(
child: DetailsScreen(),
),
// Or custom transition
pageBuilder: (context, state) => CustomTransitionPage(
child: const DetailsScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOut).animate(animation),
child: child,
);
},
),
)
GoRouter(
errorBuilder: (context, state) {
return ErrorScreen(error: state.error);
},
// Or handle 404
errorPageBuilder: (context, state) => MaterialPage(
child: NotFoundScreen(path: state.uri.path),
),
)
Define typed routes:
part 'app_routes.g.dart';
@TypedGoRoute<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData {
@override
Widget build(BuildContext context, GoRouterState state) {
return const HomeScreen();
}
}
@TypedGoRoute<UserRoute>(path: '/user/:id')
class UserRoute extends GoRouteData {
final String id;
const UserRoute({required this.id});
@override
Widget build(BuildContext context, GoRouterState state) {
return UserScreen(id: id);
}
}
Access route information in builders:
GoRoute(
path: '/details',
builder: (context, state) {
// Path: state.uri.path
// Query params: state.uri.queryParameters
// Extra data: state.extra (passed via context.go('/details', extra: data))
return DetailsScreen(
path: state.uri.path,
data: state.extra as MyData?,
);
},
)
// Extension for cleaner navigation
extension NavigationHelpers on BuildContext {
void goHome() => go('/');
void goUser(String id) => go('/user/$id');
void pushModal(Widget screen) => push('/modal', extra: screen);
}
// Usage
context.goHome();
context.goUser('123');
Enable web URL handling:
GoRouter(
// Handles browser back/forward
// URL updates automatically
initialLocation: '/',
// For base href in web
routerNeglect: true, // Disable browser history
)
// Access current route
final currentRoute = GoRouter.of(context).routeInformationProvider.value.uri.path;
GoRouter(
observers: [RouteObserver()],
navigatorBuilder: (context, state, child) {
// Wrap all routes
return SomeWrapper(child: child);
},
)
testWidgets('navigates correctly', (tester) async {
await tester.pumpWidget(
MaterialApp.router(
routerConfig: _router,
),
);
// Find and tap button
await tester.tap(find.text('Go to details'));
await tester.pumpAndSettle();
// Verify navigation
expect(find.text('Details'), findsOneWidget);
});
For type-safe routes with code generation:
dart run build_runner build
Generates:
$route constant for route locationConfigure Android & iOS for deep links, then handle in GoRouter:
GoRouter(
routes: [
GoRoute(
path: '/product/:id',
builder: (context, state) {
// Handles myapp.com/product/123
// and app://product/123
return ProductScreen(id: state.pathParameters['id']!);
},
),
],
)
// Store router reference
final GoRouter router = GoRouter(...);
// Navigate without context
router.go('/home');
router.push('/modal');
router.pop();
See BEST_PRACTICES.md for architecture patterns.