Answer
Overview
Flutter provides a powerful navigation system to move between screens (routes). Understanding navigation types and patterns is essential for building multi-screen apps.
Types of Navigation
| Type | Description | Use Case |
|---|---|---|
| Named Routes | Pre-defined routes with string identifiers | Large apps with many screens |
| Anonymous Routes | Routes created on-the-fly | Simple navigation, passing complex data |
| Generated Routes | Programmatically generated routes | Dynamic routing with parameters |
| Nested Navigation | Navigator within Navigator | Tab bars, bottom navigation |
| Deep Linking | External URLs open specific screens | Web integration, push notifications |
1. Anonymous Routes
Create routes dynamically using
MaterialPageRoutedart// Navigate to new screen Navigator.push( context, MaterialPageRoute( builder: (context) => DetailsScreen(userId: 123), ), ); // Go back Navigator.pop(context);
Complete Example
dartclass HomeScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Home')), body: Center( child: ElevatedButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailsScreen(id: 42), ), ); }, child: Text('Go to Details'), ), ), ); } } class DetailsScreen extends StatelessWidget { final int id; const DetailsScreen({required this.id}); Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Details $id')), body: Center( child: ElevatedButton( onPressed: () => Navigator.pop(context), child: Text('Go Back'), ), ), ); } }
Pros:
- ✅ Type-safe parameter passing
- ✅ Simple for few screens
- ✅ Easy to understand
Cons:
- ❌ Decentralized (routes scattered)
- ❌ Harder to maintain in large apps
2. Named Routes
Define all routes in one central location.
dartvoid main() { runApp( MaterialApp( initialRoute: '/', routes: { '/': (context) => HomeScreen(), '/profile': (context) => ProfileScreen(), '/settings': (context) => SettingsScreen(), '/details': (context) => DetailsScreen(), }, ), ); } // Navigate Navigator.pushNamed(context, '/profile'); // Go back Navigator.pop(context);
With Arguments
dart// Navigate with arguments Navigator.pushNamed( context, '/profile', arguments: {'userId': 123, 'name': 'John'}, ); // Receive arguments class ProfileScreen extends StatelessWidget { Widget build(BuildContext context) { final args = ModalRoute.of(context)!.settings.arguments as Map; final userId = args['userId']; final name = args['name']; return Scaffold( appBar: AppBar(title: Text('Profile: $name')), body: Text('User ID: $userId'), ); } }
Pros:
- ✅ Centralized route management
- ✅ Easy to track navigation flow
- ✅ Clean navigation code
Cons:
- ❌ Not type-safe (arguments are dynamic)
- ❌ Complex parameter passing
3. Generated Routes (onGenerateRoute)
Programmatically create routes with type-safe parameters.
dartclass AppRouter { static Route<dynamic> generateRoute(RouteSettings settings) { switch (settings.name) { case '/': return MaterialPageRoute(builder: (_) => HomeScreen()); case '/profile': final args = settings.arguments as ProfileArguments; return MaterialPageRoute( builder: (_) => ProfileScreen( userId: args.userId, name: args.name, ), ); case '/details': if (settings.arguments is int) { final id = settings.arguments as int; return MaterialPageRoute( builder: (_) => DetailsScreen(id: id), ); } return _errorRoute(); default: return _errorRoute(); } } static Route<dynamic> _errorRoute() { return MaterialPageRoute( builder: (_) => Scaffold( body: Center(child: Text('Route not found')), ), ); } } // Arguments class class ProfileArguments { final int userId; final String name; ProfileArguments({required this.userId, required this.name}); } // MaterialApp MaterialApp( onGenerateRoute: AppRouter.generateRoute, ) // Navigate Navigator.pushNamed( context, '/profile', arguments: ProfileArguments(userId: 123, name: 'John'), );
Pros:
- ✅ Type-safe arguments
- ✅ Centralized logic
- ✅ Custom route handling
- ✅ Error routes (404 pages)
Cons:
- ❌ More boilerplate code
4. Nested Navigation
Navigator inside another Navigator (for TabBar, BottomNavigationBar).
dartclass HomeScreen extends StatefulWidget { _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { int _currentIndex = 0; final List<GlobalKey<NavigatorState>> _navigatorKeys = [ GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), ]; Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { // Pop from current tab's navigator return !await _navigatorKeys[_currentIndex].currentState!.maybePop(); }, child: Scaffold( body: IndexedStack( index: _currentIndex, children: [ _buildNavigator(0, FeedScreen()), _buildNavigator(1, SearchScreen()), _buildNavigator(2, ProfileScreen()), ], ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: (index) => setState(() => _currentIndex = index), items: [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Feed'), BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'), BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'), ], ), ), ); } Widget _buildNavigator(int index, Widget child) { return Navigator( key: _navigatorKeys[index], onGenerateRoute: (settings) { return MaterialPageRoute(builder: (_) => child); }, ); } }
Use Cases:
- Tab navigation (keep state per tab)
- Bottom navigation bar
- Drawer navigation
- Master-detail layouts
Navigation Methods
Push (Add to Stack)
dart// Push new route Navigator.push(context, MaterialPageRoute(builder: (_) => Screen())); // Push named route Navigator.pushNamed(context, '/screen');
Pop (Remove from Stack)
dart// Go back Navigator.pop(context); // Go back with result Navigator.pop(context, {'success': true});
Push Replacement
Replace current route with new one.
dart// Replace current route Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => NewScreen()), ); // Named route Navigator.pushReplacementNamed(context, '/new');
Use Case: Login → Home (remove login from stack)
Push and Remove Until
Navigate and clear stack until condition.
dart// Clear all routes and go to home Navigator.pushNamedAndRemoveUntil( context, '/home', (route) => false, // Remove all routes ); // Keep only first route Navigator.pushNamedAndRemoveUntil( context, '/success', ModalRoute.withName('/'), // Keep root route );
Use Case: Logout → Login (clear all screens)
Pop Until
Pop routes until condition is met.
dart// Pop until home screen Navigator.popUntil(context, ModalRoute.withName('/home')); // Pop until first route Navigator.popUntil(context, (route) => route.isFirst);
Can Pop
Check if there are routes to pop.
dartif (Navigator.canPop(context)) { Navigator.pop(context); } else { // Already at root, maybe exit app SystemNavigator.pop(); }
Passing and Returning Data
Forward Data
dart// Send data forward final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => SelectionScreen(options: ['A', 'B', 'C']), ), ); print('Selected: $result');
Return Data
dartclass SelectionScreen extends StatelessWidget { final List<String> options; const SelectionScreen({required this.options}); Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: options.length, itemBuilder: (context, index) { return ListTile( title: Text(options[index]), onTap: () { // Return selected value Navigator.pop(context, options[index]); }, ); }, ), ); } }
Custom Transitions
dartclass FadeRoute<T> extends PageRouteBuilder<T> { final Widget page; FadeRoute({required this.page}) : super( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition(opacity: animation, child: child); }, transitionDuration: Duration(milliseconds: 500), ); } // Usage Navigator.push(context, FadeRoute(page: DetailsScreen()));
Deep Linking
Handle external URLs opening specific screens.
dartMaterialApp( onGenerateInitialRoutes: (String initialRoute) { // Handle deep links if (initialRoute.startsWith('/product/')) { final productId = initialRoute.split('/').last; return [ MaterialPageRoute(builder: (_) => HomeScreen()), MaterialPageRoute( builder: (_) => ProductScreen(id: int.parse(productId)), ), ]; } return [MaterialPageRoute(builder: (_) => HomeScreen())]; }, )
Best Practices
Important: Use named routes for maintainability in large apps
✅ Do
dart// Good - Route constants class Routes { static const home = '/'; static const profile = '/profile'; static const settings = '/settings'; } Navigator.pushNamed(context, Routes.profile); // Good - Type-safe arguments class RouteArguments { final int id; final String name; RouteArguments({required this.id, required this.name}); } // Good - Handle back button WillPopScope( onWillPop: () async { // Custom back button logic return true; // Allow pop }, child: Scaffold(...), )
❌ Don't
dart// Bad - Magic strings Navigator.pushNamed(context, '/profile'); // Typo-prone // Bad - Complex logic in UI Navigator.push( context, MaterialPageRoute( builder: (_) { // ❌ Complex logic here final user = fetchUser(); return ProfileScreen(user: user); }, ), );