Answer
Overview
Routes in Flutter represent different screens or pages in your app. Route management allows navigation between these screens, similar to URLs in web development.
Types of Routes
1. Named Routes
Routes defined with string identifiers in a central location.
dartMaterialApp( initialRoute: '/', routes: { '/': (context) => HomeScreen(), '/profile': (context) => ProfileScreen(), '/settings': (context) => SettingsScreen(), }, )
Navigation:
dart// Push Navigator.pushNamed(context, '/profile'); // Pop Navigator.pop(context); // Replace Navigator.pushReplacementNamed(context, '/home');
Pros:
- ✅ Centralized route management
- ✅ Easy to maintain
- ✅ Clean navigation code
Cons:
- ❌ Harder to pass parameters
- ❌ No type safety
2. Anonymous Routes
Routes created on-the-fly using MaterialPageRoute.
dartNavigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen(userId: 123), ), );
Pros:
- ✅ Easy to pass parameters
- ✅ Type-safe
- ✅ Flexible
Cons:
- ❌ Decentralized (routes scattered across code)
- ❌ Harder to track navigation flow
3. Generated Routes
Routes generated programmatically with onGenerateRoute.
dartMaterialApp( onGenerateRoute: (settings) { if (settings.name == '/profile') { final args = settings.arguments as Map<String, dynamic>; return MaterialPageRoute( builder: (context) => ProfileScreen(userId: args['id']), ); } return null; }, )
Pros:
- ✅ Type-safe parameter passing
- ✅ Centralized route logic
- ✅ Custom route handling
Navigation Methods
Push (Add to Stack)
dart// Named route Navigator.pushNamed(context, '/settings'); // Anonymous route Navigator.push( context, MaterialPageRoute(builder: (context) => SettingsScreen()), );
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.
dartNavigator.pushReplacementNamed(context, '/home');
Push and Remove Until
Navigate and clear stack until condition.
dart// Clear all and go to home Navigator.pushNamedAndRemoveUntil( context, '/home', (route) => false, ); // Keep only first route Navigator.pushNamedAndRemoveUntil( context, '/success', ModalRoute.withName('/'), );
Passing Data Between Routes
Method 1: Constructor (Anonymous Routes)
dart// Navigate with data Navigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen( userId: 123, userName: 'John', ), ), ); // Receive data class ProfileScreen extends StatelessWidget { final int userId; final String userName; const ProfileScreen({required this.userId, required this.userName}); Widget build(BuildContext context) { return Text('User: $userName'); } }
Method 2: Arguments (Named Routes)
dart// Navigate with arguments Navigator.pushNamed( context, '/profile', arguments: {'id': 123, 'name': 'John'}, ); // Receive arguments class ProfileScreen extends StatelessWidget { Widget build(BuildContext context) { final args = ModalRoute.of(context)!.settings.arguments as Map; return Text('User: ${args['name']}'); } }
Method 3: Return Data (Pop with Result)
dart// Screen 1: Wait for result final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => SelectionScreen()), ); print('Selected: $result'); // Screen 2: Return result Navigator.pop(context, 'Selected Item');
Complete Example
dartvoid main() => runApp(MyApp()); class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Routes Demo', initialRoute: '/', routes: { '/': (context) => HomeScreen(), '/details': (context) => DetailsScreen(), }, onGenerateRoute: (settings) { // Handle dynamic routes if (settings.name == '/profile') { final args = settings.arguments as Map; return MaterialPageRoute( builder: (context) => ProfileScreen(userId: args['id']), ); } return null; }, onUnknownRoute: (settings) { // 404 page return MaterialPageRoute(builder: (context) => NotFoundScreen()); }, ); } } class HomeScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Home')), body: Column( children: [ ElevatedButton( onPressed: () => Navigator.pushNamed(context, '/details'), child: Text('Go to Details'), ), ElevatedButton( onPressed: () { Navigator.pushNamed( context, '/profile', arguments: {'id': 123}, ); }, child: Text('Go to Profile'), ), ], ), ); } } class DetailsScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Details')), body: Center( child: ElevatedButton( onPressed: () => Navigator.pop(context), child: Text('Go Back'), ), ), ); } }
Route Transitions
Custom Page Route
dartclass FadeRoute extends PageRouteBuilder { final Widget page; FadeRoute({required this.page}) : super( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition(opacity: animation, child: child); }, ); } // Usage Navigator.push(context, FadeRoute(page: ProfileScreen()));
Best Practices
Tip: Use named routes for large apps, anonymous routes for small apps
- ✅ Use named routes for maintainability
- ✅ Keep route names as constants
- ✅ Handle unknown routes with onUnknownRoute
- ✅ Use type-safe arguments
- ✅ Avoid deep navigation stacks
- ❌ Don't mix navigation approaches
dart// Good - Route constants class Routes { static const home = '/'; static const profile = '/profile'; static const settings = '/settings'; } Navigator.pushNamed(context, Routes.profile);