Navigation Basics
Moving Between Screens with Navigator
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Flutter's Navigator manages a stack of Route objects representing screens. Navigator.push adds a screen on top, Navigator.pop removes it, and MaterialPageRoute provides platform-appropriate animations. You can pass data forward through constructor parameters and backward through pop results. Named routes provide string-based navigation, while pushReplacement and pushAndRemoveUntil control the back stack.
Real-world relevance
In team_mvp_kit, basic Navigator is used for simple screen transitions before go_router is introduced. The login flow uses pushReplacement to prevent going back to login after authentication. The app uses pushAndRemoveUntil on logout to clear the entire navigation stack. Understanding Navigator basics is essential before learning go_router.
Key points
- Navigator.push — Navigator.push adds a new screen (route) on top of the navigation stack. The current screen stays in memory underneath. Use MaterialPageRoute to wrap the destination widget. The push method returns a Future that resolves when the pushed screen is popped — this is how you get data back from a screen.
- Navigator.pop — Navigator.pop removes the current screen from the stack, revealing the previous screen underneath. You can optionally pass data back to the previous screen by providing a result argument. The calling screen receives this result through the Future returned by push.
- Receiving Data from Pop — When you push a screen, you get a Future that completes when that screen pops. Await it to receive any data the popped screen returned. This pattern is used for selection screens, dialog results, and form submissions where the previous screen needs the result.
- MaterialPageRoute — MaterialPageRoute creates a platform-appropriate transition animation when navigating. On Android it slides up from the bottom, on iOS it slides in from the right. It takes a builder function that receives BuildContext and returns the destination widget. Set fullscreenDialog: true for a modal-style slide-up animation.
- Named Routes — Named routes let you navigate by string names instead of building MaterialPageRoute each time. Define routes in MaterialApp's routes map, then use Navigator.pushNamed(context, '/detail'). Named routes are simpler but limited — they cannot easily pass complex arguments. Modern Flutter prefers go_router instead.
- Navigator.pushReplacement — pushReplacement removes the current screen and replaces it with a new one. The user cannot go back to the replaced screen. Use it for login-to-home transitions (after login, you don't want users to go back to login) or splash screen to main app transitions.
- Navigator.pushAndRemoveUntil — pushAndRemoveUntil pushes a new screen and removes all previous screens from the stack until the predicate returns true. Passing (route) => false removes ALL previous screens. Use it for logout (clear entire stack and show login) or after onboarding (clear onboarding screens).
- WillPopScope and PopScope — PopScope (replacing the deprecated WillPopScope) intercepts the back button press. Set canPop: false to prevent going back, and use onPopInvokedWithResult to show a confirmation dialog before leaving. Use it for forms with unsaved changes or checkout flows where accidental back presses lose data.
Code example
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _selectedFruit = 'None selected';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Selected: $_selectedFruit',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () async {
final result = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => const FruitPickerScreen(),
),
);
if (result != null) {
setState(() => _selectedFruit = result);
}
},
child: const Text('Pick a Fruit'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
},
child: const Text('Settings'),
),
],
),
),
);
}
}
class FruitPickerScreen extends StatelessWidget {
const FruitPickerScreen({super.key});
@override
Widget build(BuildContext context) {
final fruits = ['Apple', 'Banana', 'Cherry', 'Durian', 'Elderberry'];
return Scaffold(
appBar: AppBar(title: const Text('Pick a Fruit')),
body: ListView.builder(
itemCount: fruits.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(fruits[index]),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// Pop and return the selected fruit
Navigator.pop(context, fruits[index]);
},
);
},
),
);
}
}
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Simulate logout: clear stack and go to login
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const LoginScreen(),
),
(route) => false,
);
},
child: const Text('Logout'),
),
),
);
}
}
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Center(
child: ElevatedButton(
onPressed: () {
// After login, replace login with home
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
},
child: const Text('Login'),
),
),
);
}
}Line-by-line walkthrough
- 1. Import the Material package
- 2.
- 3. Define HomeScreen as a StatefulWidget
- 4.
- 5.
- 6. Create the State class
- 7. Track which fruit was selected, starting with none
- 8.
- 9. Build method creates the UI
- 10. Scaffold with Home title in AppBar
- 11. Center the content
- 12. Column with centered alignment
- 13.
- 14. Display the currently selected fruit
- 15. Style with headlineSmall from theme
- 16.
- 17. 24px gap
- 18. ElevatedButton to open the fruit picker
- 19. Await the result from Navigator.push
- 20. Push a MaterialPageRoute to FruitPickerScreen
- 21.
- 22.
- 23.
- 24. If a fruit was selected (not null), update state
- 25.
- 26.
- 27.
- 28. 12px gap
- 29. Another ElevatedButton for settings
- 30. Push to SettingsScreen (no result expected)
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41. FruitPickerScreen is a StatelessWidget
- 42.
- 43.
- 44. Define a list of fruit names
- 45.
- 46. Scaffold with Pick a Fruit AppBar
- 47. ListView.builder for the fruit list
- 48. Build a ListTile for each fruit
- 49. Display the fruit name
- 50. Chevron icon as trailing indicator
- 51. On tap, pop with the selected fruit name
- 52. Navigator.pop passes the fruit string back to HomeScreen
- 53.
- 54.
- 55.
- 56.
- 57.
- 58. SettingsScreen is a StatelessWidget
- 59.
- 60. Scaffold with Settings AppBar
- 61. Logout button in the center
- 62. On press, clear the entire navigation stack
- 63. pushAndRemoveUntil pushes LoginScreen
- 64.
- 65.
- 66. (route) => false removes ALL previous routes
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73. LoginScreen is a StatelessWidget
- 74.
- 75. Scaffold with Login AppBar
- 76. Login button in the center
- 77. On press, replace login screen with home
- 78. pushReplacement ensures user cannot go back to login
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
Spot the bug
ElevatedButton(
onPressed: () {
final result = Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => SelectionScreen(),
),
);
setState(() => selected = result);
},
child: Text('Select'),
)Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Navigation and Routing (Flutter Official)
- Navigate to a New Screen and Back (Flutter Official)
- Return Data from a Screen (Flutter Official)