Answer
Overview
GoRouter is the official Flutter routing package from the Flutter team, built on Navigator 2.0. It provides declarative routing, deep linking, and web support with simpler syntax than Navigator 2.0.
Installation
Add to
text
pubspec.yamlyamldependencies: go_router: ^13.0.0
Basic Setup
dartimport 'package:go_router/go_router.dart'; final router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => HomePage(), ), GoRoute( path: '/profile/:userId', builder: (context, state) { final userId = state.pathParameters['userId']!; return ProfilePage(userId: userId); }, ), ], ); void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp.router( routerConfig: router, ); } }
Navigation Methods
Go (Replace Current Route)
dart// Navigate to route (replaces current) context.go('/profile/123'); // With query parameters context.go('/search?query=flutter&page=2');
Push (Add to Stack)
dart// Push new route onto stack context.push('/profile/123'); // Async navigation (wait for result) final result = await context.push('/edit-profile'); print('Result: $result');
Pop
dart// Go back context.pop(); // Pop with result context.pop('success');
Route Parameters
Path Parameters
dartGoRoute( path: '/user/:userId/post/:postId', builder: (context, state) { final userId = state.pathParameters['userId']!; final postId = state.pathParameters['postId']!; return PostPage(userId: userId, postId: postId); }, ) // Navigate context.go('/user/123/post/456');
Query Parameters
dartGoRoute( path: '/search', builder: (context, state) { final query = state.uri.queryParameters['query']; final page = int.tryParse(state.uri.queryParameters['page'] ?? '1'); return SearchPage(query: query, page: page); }, ) // Navigate context.go('/search?query=flutter&page=2');
Extra Data (Complex Objects)
dartGoRoute( path: '/details', builder: (context, state) { final user = state.extra as User; // Pass complex object return DetailsPage(user: user); }, ) // Navigate with extra data final user = User(id: '123', name: 'Alice'); context.go('/details', extra: user);
Nested Routes (Children)
dartGoRoute( path: '/', builder: (context, state) => HomePage(), routes: [ GoRoute( path: 'profile', // Full path: /profile builder: (context, state) => ProfilePage(), ), GoRoute( path: 'settings', // Full path: /settings builder: (context, state) => SettingsPage(), ), ], )
ShellRoute (Persistent UI)
Keep UI elements (like bottom nav) while navigating between child routes.
dartfinal router = GoRouter( routes: [ ShellRoute( builder: (context, state, child) { return ScaffoldWithNavBar(child: child); // Persistent bottom nav }, routes: [ GoRoute( path: '/', builder: (context, state) => HomePage(), ), GoRoute( path: '/profile', builder: (context, state) => ProfilePage(), ), GoRoute( path: '/settings', builder: (context, state) => SettingsPage(), ), ], ), ], ); class ScaffoldWithNavBar extends StatelessWidget { final Widget child; const ScaffoldWithNavBar({required this.child}); Widget build(BuildContext context) { return Scaffold( body: child, bottomNavigationBar: BottomNavigationBar( onTap: (index) { switch (index) { case 0: context.go('/'); case 1: context.go('/profile'); case 2: context.go('/settings'); } }, items: [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'), BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'), ], ), ); } }
Redirects (Guards)
Protect routes with authentication.
dartfinal router = GoRouter( redirect: (context, state) { final isLoggedIn = authService.isLoggedIn; final isGoingToLogin = state.matchedLocation == '/login'; // Not logged in → redirect to login if (!isLoggedIn && !isGoingToLogin) { return '/login'; } // Logged in + on login page → redirect home if (isLoggedIn && isGoingToLogin) { return '/'; } return null; // No redirect }, routes: [ GoRoute(path: '/login', builder: (context, state) => LoginPage()), GoRoute(path: '/', builder: (context, state) => HomePage()), GoRoute(path: '/profile', builder: (context, state) => ProfilePage()), ], );
Error Handling
Custom 404 page.
dartfinal router = GoRouter( errorBuilder: (context, state) { return ErrorPage(error: state.error); }, routes: [ GoRoute(path: '/', builder: (context, state) => HomePage()), ], ); class ErrorPage extends StatelessWidget { final Exception? error; const ErrorPage({this.error}); Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Error')), body: Center(child: Text('Page not found: ${error?.toString()}')), ); } }
Deep Linking
GoRouter automatically handles deep links.
dart// Deep link: myapp://product/123 // Navigates to ProductPage with id='123' GoRoute( path: '/product/:id', builder: (context, state) { final id = state.pathParameters['id']!; return ProductPage(productId: id); }, )
Android setup (AndroidManifest.xml):
xml<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" /> </intent-filter>
Route Observers
Track navigation for analytics.
dartclass MyObserver extends NavigatorObserver { void didPush(Route route, Route? previousRoute) { print('Pushed: ${route.settings.name}'); } } final router = GoRouter( observers: [MyObserver()], routes: [...], );
Named Routes
dartfinal router = GoRouter( routes: [ GoRoute( path: '/', name: 'home', builder: (context, state) => HomePage(), ), GoRoute( path: '/profile/:userId', name: 'profile', builder: (context, state) { final userId = state.pathParameters['userId']!; return ProfilePage(userId: userId); }, ), ], ); // Navigate by name context.goNamed('profile', pathParameters: {'userId': '123'});
Refresh Listenable (State Changes)
Rebuild routes when state changes (auth status, etc.).
dartfinal router = GoRouter( refreshListenable: authService, // Listens to ChangeNotifier redirect: (context, state) { final isLoggedIn = authService.isLoggedIn; if (!isLoggedIn) return '/login'; return null; }, routes: [...], ); class AuthService extends ChangeNotifier { bool _isLoggedIn = false; bool get isLoggedIn => _isLoggedIn; void login() { _isLoggedIn = true; notifyListeners(); // Triggers GoRouter refresh } }
Comparison: Navigator 2.0 vs GoRouter
| Feature | Navigator 2.0 | GoRouter |
|---|---|---|
| Syntax | Imperative | Declarative ✅ |
| Boilerplate | High | Low ✅ |
| Deep linking | Manual | Automatic ✅ |
| Web URLs | Complex | Simple ✅ |
| Guards | Manual | text |
| Type safety | Manual | Path params ✅ |
Full Example
dartimport 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; void main() { runApp(MyApp()); } final router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => HomePage(), ), GoRoute( path: '/profile/:userId', builder: (context, state) { final userId = state.pathParameters['userId']!; return ProfilePage(userId: userId); }, ), ], ); class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp.router( routerConfig: router, ); } } class HomePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Home')), body: Center( child: ElevatedButton( onPressed: () => context.go('/profile/123'), child: Text('Go to Profile'), ), ), ); } } class ProfilePage extends StatelessWidget { final String userId; const ProfilePage({required this.userId}); Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Profile')), body: Center(child: Text('User ID: $userId')), ); } }
Best Practices
dart// ✅ Use go() for navigation (declarative) context.go('/profile'); // ✅ Use named routes for type safety context.goNamed('profile', pathParameters: {'id': '123'}); // ✅ Use ShellRoute for persistent UI ShellRoute(builder: ..., routes: [...]) // ✅ Use redirect for auth guards redirect: (context, state) => isLoggedIn ? null : '/login' // ❌ Don't mix GoRouter with Navigator.push
Learn more: GoRouter Documentation