What is the difference between Navigator and Router in Flutter?
#flutter#navigation#routing#navigator#router
Answer
Overview
Navigator and Router are two different approaches to handling navigation in Flutter. Navigator represents the traditional imperative navigation (Navigator 1.0), while Router provides a declarative navigation system (Navigator 2.0). Understanding both is essential for choosing the right navigation strategy.
Key Differences
| Feature | Navigator (1.0) | Router (2.0) |
|---|---|---|
| Approach | Imperative | Declarative |
| Navigation Style | Command-based (push/pop) | State-based |
| Stack Control | Limited (top only) | Full control |
| Deep Linking | Manual implementation | Built-in support |
| URL Synchronization | Not supported | Automatic |
| Web Support | Basic | Excellent |
| Complexity | Simple | More complex |
| Boilerplate | Minimal | Higher |
| State Restoration | Limited | Full support |
| Use Case | Simple apps | Complex apps, web |
Navigator (Imperative Navigation)
How It Works
Navigator uses an imperative approach where you explicitly tell Flutter what to do using methods like
text
push()text
pop()Basic Example
dart// Push a new screen Navigator.push( context, MaterialPageRoute(builder: (context) => DetailScreen()), ); // Pop current screen Navigator.pop(context); // Push with named routes Navigator.pushNamed(context, '/details'); // Replace current route Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => HomeScreen()), ); // Clear stack and push Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => LoginScreen()), (route) => false, );
Named Routes Setup
dartclass MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( initialRoute: '/', routes: { '/': (context) => HomeScreen(), '/details': (context) => DetailScreen(), '/settings': (context) => SettingsScreen(), }, ); } }
Advantages of Navigator
- Simple to learn and use
- Minimal boilerplate code
- Quick setup for basic apps
- Straightforward for linear navigation flows
Limitations of Navigator
- Limited stack control - can only add to top or remove from top
- No URL synchronization for web
- Manual deep linking implementation required
- Harder to test navigation logic
- Tightly coupled to UI code
Router (Declarative Navigation)
How It Works
Router uses a declarative approach where navigation is a function of app state. You describe what the navigation stack should look like, and Flutter updates it accordingly.
Core Components
- RouterDelegate - Builds the Navigator widget based on app state
- RouteInformationParser - Converts URLs to app state
- RouteInformationProvider - Provides route information to the router
Basic Example
dartclass AppRouterDelegate extends RouterDelegate<AppRoutePath> with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> { final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); bool _showDetails = false; String? _selectedId; // App state getters bool get showDetails => _showDetails; String? get selectedId => _selectedId; // Methods to update state void showHomePage() { _showDetails = false; notifyListeners(); } void showDetailsPage(String id) { _selectedId = id; _showDetails = true; notifyListeners(); } Widget build(BuildContext context) { // Navigation stack is built declaratively based on state return Navigator( key: navigatorKey, pages: [ MaterialPage( key: ValueKey('home'), child: HomeScreen( onItemTapped: (id) => showDetailsPage(id), ), ), if (_showDetails) MaterialPage( key: ValueKey('details-$_selectedId'), child: DetailScreen(id: _selectedId!), ), ], onPopPage: (route, result) { if (!route.didPop(result)) { return false; } // Update state when page is popped _showDetails = false; notifyListeners(); return true; }, ); } AppRoutePath get currentConfiguration { if (_showDetails) { return AppRoutePath.details(_selectedId!); } return AppRoutePath.home(); } Future<void> setNewRoutePath(AppRoutePath path) async { if (path.isDetailsPage) { showDetailsPage(path.id!); } else { showHomePage(); } } } class AppRouteInformationParser extends RouteInformationParser<AppRoutePath> { Future<AppRoutePath> parseRouteInformation( RouteInformation routeInformation) async { final uri = Uri.parse(routeInformation.location!); // Handle '/' if (uri.pathSegments.isEmpty) { return AppRoutePath.home(); } // Handle '/details/:id' if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'details') { return AppRoutePath.details(uri.pathSegments[1]); } return AppRoutePath.home(); } RouteInformation restoreRouteInformation(AppRoutePath path) { if (path.isDetailsPage) { return RouteInformation(location: '/details/${path.id}'); } return RouteInformation(location: '/'); } } // Route path model class AppRoutePath { final String? id; final bool isDetailsPage; AppRoutePath.home() : id = null, isDetailsPage = false; AppRoutePath.details(this.id) : isDetailsPage = true; } // App setup class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp.router( routerDelegate: AppRouterDelegate(), routeInformationParser: AppRouteInformationParser(), ); } }
Advantages of Router
- Full stack control - manipulate entire navigation stack
- Built-in deep linking support
- URL synchronization for web apps
- State restoration automatically handled
- Better testability - navigation is pure function of state
- Declarative - easier to reason about complex flows
- Web-friendly - proper browser back/forward support
Limitations of Router
- Steeper learning curve
- More boilerplate code
- Complex setup for simple apps
- Verbose implementation
Comparison Example
Scenario: Navigate to details page and back
Navigator (Imperative):
dart// In HomeScreen ListTile( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailScreen(id: '123'), ), ); }, title: Text('Item'), ) // In DetailScreen ElevatedButton( onPressed: () { Navigator.pop(context); }, child: Text('Back'), )
Router (Declarative):
dart// Update state to show details void showDetailsPage(String id) { _selectedId = id; _showDetails = true; notifyListeners(); // Navigator rebuilds automatically } // Update state to go back void showHomePage() { _showDetails = false; notifyListeners(); // Navigator rebuilds automatically }
When to Use Each
Use Navigator When:
- Building simple mobile apps
- Need quick prototyping
- Navigation is straightforward and linear
- No deep linking required
- Mobile-only application
- Team prefers simplicity over features
Use Router When:
- Building web applications
- Need deep linking support
- Require URL synchronization
- Complex authentication flows
- Need state restoration
- Want full control over navigation stack
- Building production apps with complex navigation
Hybrid Approach
You can also use packages like
text
go_routerdartfinal router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => HomeScreen(), ), GoRoute( path: '/details/:id', builder: (context, state) { final id = state.pathParameters['id']!; return DetailScreen(id: id); }, ), ], ); class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp.router( routerConfig: router, ); } }
Best Practices
- Start with Navigator for learning and simple apps
- Use Router (or go_router) for production web apps
- Don't mix imperative and declarative patterns unnecessarily
- Consider packages like go_router for easier Router usage
- Test navigation thoroughly in both approaches