Files
minimax-skills/skills/flutter-dev/references/gorouter-navigation.md

258 lines
6.1 KiB
Markdown
Raw Permalink Normal View History

# GoRouter Navigation
GoRouter navigation guide covering route setup, guards, deep linking, and shell routes.
## Basic Setup
```dart
import 'package:go_router/go_router.dart';
final goRouter = GoRouter(
initialLocation: '/',
debugLogDiagnostics: true,
redirect: (context, state) {
final isLoggedIn = /* check auth state */;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
if (!isLoggedIn && !isAuthRoute) {
return '/auth/login';
}
if (isLoggedIn && isAuthRoute) {
return '/';
}
return null;
},
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
name: 'details',
builder: (context, state) {
final id = state.pathParameters['id']!;
final extra = state.extra as Map<String, dynamic>?;
return DetailsScreen(id: id, title: extra?['title']);
},
),
],
),
GoRoute(
path: '/auth/login',
name: 'login',
builder: (context, state) => const LoginScreen(),
),
],
);
```
### App Integration
```dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: goRouter,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: ThemeMode.system,
);
}
}
```
## Navigation Methods
```dart
// Navigate and replace entire stack
context.go('/details/123');
// Navigate and add to stack (can go back)
context.push('/details/123');
// Go back
context.pop();
// Go back with result
context.pop(result);
// Replace current route
context.pushReplacement('/home');
// Navigate with extra data
context.push('/details/123', extra: {'title': 'Item Title'});
// Navigate by name
context.goNamed('details', pathParameters: {'id': '123'});
context.pushNamed('details', pathParameters: {'id': '123'}, extra: data);
```
### Navigation Reference
| Method | Behavior |
|--------|----------|
| `context.go()` | Navigate, replace entire stack |
| `context.push()` | Navigate, add to stack |
| `context.pop()` | Go back one level |
| `context.pushReplacement()` | Replace current route |
| `context.goNamed()` | Navigate by route name |
| `context.canPop()` | Check if can go back |
## Shell Routes (Persistent UI)
```dart
final goRouter = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) {
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(
path: '/home',
builder: (_, __) => const HomeScreen(),
),
GoRoute(
path: '/search',
builder: (_, __) => const SearchScreen(),
),
GoRoute(
path: '/profile',
builder: (_, __) => const ProfileScreen(),
),
],
),
],
);
class ScaffoldWithNavBar extends StatelessWidget {
final Widget child;
const ScaffoldWithNavBar({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: _calculateSelectedIndex(context),
onDestinationSelected: (index) => _onItemTapped(index, context),
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.search), label: 'Search'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
int _calculateSelectedIndex(BuildContext context) {
final location = GoRouterState.of(context).matchedLocation;
if (location.startsWith('/home')) return 0;
if (location.startsWith('/search')) return 1;
if (location.startsWith('/profile')) return 2;
return 0;
}
void _onItemTapped(int index, BuildContext context) {
switch (index) {
case 0: context.go('/home');
case 1: context.go('/search');
case 2: context.go('/profile');
}
}
}
```
## Query Parameters
```dart
GoRoute(
path: '/search',
builder: (context, state) {
final query = state.uri.queryParameters['q'] ?? '';
final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;
return SearchScreen(query: query, page: page);
},
),
// Navigate with query params
context.go('/search?q=flutter&page=2');
context.goNamed('search', queryParameters: {'q': 'flutter', 'page': '2'});
```
## Riverpod Integration
```dart
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authProvider);
return GoRouter(
refreshListenable: authState,
redirect: (context, state) {
final isLoggedIn = authState.isAuthenticated;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
if (!isLoggedIn && !isAuthRoute) return '/auth/login';
if (isLoggedIn && isAuthRoute) return '/';
return null;
},
routes: [...],
);
});
// In app.dart
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);
}
}
```
## Error Handling
```dart
final goRouter = GoRouter(
errorBuilder: (context, state) {
return ErrorScreen(error: state.error);
},
routes: [...],
);
```
## Deep Linking
Deep links work automatically when routes are configured with path parameters:
```dart
// URL: myapp://details/123
// or: https://myapp.com/details/123
GoRoute(
path: '/details/:id',
builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!),
),
```
## Best Practices
| Do | Don't |
|----|-------|
| Use named routes for maintainability | Hardcode paths everywhere |
| Use `push()` for detail screens | Use `go()` for all navigation |
| Pass simple data via `extra` | Pass complex objects via URL |
| Use redirect for auth guards | Check auth in every screen |
| Use ShellRoute for persistent UI | Rebuild nav bar in every screen |
---
*GoRouter is an open-source navigation package for Flutter.*