Navigator Push and Pop in Flutter
Flutter applications commonly contain multiple screens, such as a Home screen, Login screen, Product Details screen, Profile screen, Settings screen, and Checkout screen. Flutter uses the Navigator to move between these screens. In Flutter terminology, screens and pages are called routes.
The two fundamental Navigator operations are Navigator.push(), which opens a new screen, and Navigator.pop(), which closes the current screen and returns to the previous screen.
1. What is Navigator in Flutter?
The Navigator is a Flutter widget that manages a stack of routes. Each time a new screen is opened, a route is added to the stack. When the current screen is closed, that route is removed from the stack.
For example, suppose an application has these screens:
- Home Screen
- Product Screen
- Product Details Screen
The navigation stack can be visualized as:
Home Screen
↓
Product Screen
↓
Product Details Screen
When the user presses the back button from Product Details, the current route is removed and the user returns to Product Screen.
2. Understanding the Navigator Stack
Flutter's Navigator maintains a stack of routes. A stack follows the LIFO (Last In, First Out) principle.
| Operation | Effect |
|---|
| Initial screen | Home route is placed on the stack |
push() | Adds a new route to the top |
Another push() | Adds another route to the top |
pop() | Removes the top route |
Example:
Navigator Stack
Before push:
[ Home ]
After push(ProductScreen):
[ Home ]
[ ProductScreen ]
After push(DetailsScreen):
[ Home ]
[ ProductScreen ]
[ DetailsScreen ]
After pop():
[ Home ]
[ ProductScreen ]
3. What is Navigator.push()?
Navigator.push() is used to navigate from the current screen to a new screen. It adds a new route to the Navigator's route stack.
Flutter's official navigation documentation demonstrates using Navigator.push() together with MaterialPageRoute to open another screen.
Basic Syntax
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
How it works
- The user performs an action, such as tapping a button.
Navigator.push() is called.
- A new
Route is created.
- The new route is added to the Navigator stack.
- The new screen becomes visible.
4. What is MaterialPageRoute?
MaterialPageRoute creates a route using Material Design navigation behavior and provides a platform-appropriate transition animation.
Example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
The builder function returns the widget that should be displayed on the new route.
5. What is Navigator.pop()?
Navigator.pop() is used to remove the current route from the Navigator stack and return to the previous route.
Basic Syntax
Navigator.pop(context);
For example, if the current screen is DetailsScreen and the previous screen is HomeScreen, calling Navigator.pop(context) closes DetailsScreen and displays HomeScreen.
6. Basic Navigator Push and Pop Example
The following example creates two screens. The first screen uses Navigator.push() to open the second screen, while the second screen uses Navigator.pop() to return to the first screen.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Navigator Example',
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
},
child: const Text('Open Second Screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
),
);
}
}
Working of the Example
- The application starts with
HomeScreen.
- The user taps Open Second Screen.
Navigator.push() adds SecondScreen to the navigation stack.
SecondScreen becomes visible.
- The user taps Go Back.
Navigator.pop() removes SecondScreen.
- The application returns to
HomeScreen.
7. Navigation Flow Diagram
HomeScreen
|
| Navigator.push()
↓
SecondScreen
|
| Navigator.pop()
↓
HomeScreen
8. Navigator.of(context).push()
You can also access the Navigator using Navigator.of(context).
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
This is functionally similar to:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
Both approaches access the Navigator associated with the provided BuildContext.
9. Using AppBar Back Button
When a new route is pushed using a standard Material route, Flutter generally provides an appropriate back navigation control in the AppBar. The user can also create a custom back button.
AppBar(
title: const Text('Details'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
)
Here, tapping the arrow calls Navigator.pop(context) and returns to the previous route.
10. Navigating from a List to a Details Screen
A common real-world use case is opening a details page when the user taps an item in a list.
import 'package:flutter/material.dart';
class ProductListScreen extends StatelessWidget {
const ProductListScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.phone_android),
title: const Text('Smartphone'),
subtitle: const Text('View product details'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(),
),
);
},
),
ListTile(
leading: const Icon(Icons.laptop),
title: const Text('Laptop'),
subtitle: const Text('View product details'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(),
),
);
},
),
],
),
);
}
}
class ProductDetailsScreen extends StatelessWidget {
const ProductDetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Products'),
),
),
);
}
}
11. Passing Data to a New Screen
In real applications, you often need to send data from one screen to another. A simple approach is to pass the data through the destination screen's constructor.
Example
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
const String productName = 'Flutter Course';
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductScreen(
name: productName,
),
),
);
},
child: const Text('View Product'),
),
),
);
}
}
class ProductScreen extends StatelessWidget {
final String name;
const ProductScreen({
super.key,
required this.name,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product'),
),
body: Center(
child: Text(
'Product: $name',
style: const TextStyle(fontSize: 22),
),
),
);
}
}
Here, the name value is passed from HomeScreen to ProductScreen.
12. Returning Data with Navigator.pop()
Navigator.pop() can also return a result to the previous screen.
Example
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
The second screen can return a value:
Navigator.pop(context, 'Flutter selected');
The complete example is:
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String message = 'No selection';
Future<void> openSelectionScreen() async {
final result = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (result != null) {
setState(() {
message = result;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(message),
const SizedBox(height: 20),
ElevatedButton(
onPressed: openSelectionScreen,
child: const Text('Open Selection'),
),
],
),
),
);
}
}
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Selection'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Flutter selected');
},
child: const Text('Select Flutter'),
),
),
);
}
}
In this example, the first screen waits for the second screen to return a value. The second screen returns the value using Navigator.pop(context, result).
13. Navigator.push() vs Navigator.pop()
| Feature | Navigator.push() | Navigator.pop() |
|---|
| Purpose | Opens a new route | Closes the current route |
| Stack operation | Adds a route | Removes a route |
| Typical use | Navigate forward | Navigate back |
| Common syntax | Navigator.push(context, route) | Navigator.pop(context) |
| Can return data? | Can receive a returned result through await | Can return a result using the second argument |
14. Multiple Screens with Push and Pop
Suppose an application contains three screens:
Home
↓ push
Products
↓ push
Details
If the user calls Navigator.pop(context) from Details:
Home
↓
Products
If the user calls Navigator.pop(context) again:
Home
Every pop() removes the current top route from the stack.
15. Checking Whether a Route Can Be Popped
Before popping a route, you may need to check whether there is a previous route available.
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
Navigator.canPop(context) returns true when the Navigator can remove the current route and reveal another route.
16. pushReplacement()
Sometimes you do not want the user to return to the current screen. In such cases, pushReplacement() can replace the current route with a new route.
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
A common example is moving from a Login screen to a Home screen after successful authentication so that the user does not simply return to the Login screen using the back navigation.
17. pushAndRemoveUntil()
pushAndRemoveUntil() pushes a new route and removes previous routes according to a condition.
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
(route) => false,
);
This pattern can be useful when you want to navigate to a new destination and clear the previous navigation history.
18. Cupertino Navigation
For Cupertino-style applications, Flutter provides CupertinoPageRoute.
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const SecondScreen(),
),
);
The destination can still be closed using:
Navigator.pop(context);
19. Common Real-World Uses of Push and Pop
| Scenario | Navigation Operation |
|---|
| Home → Product Details | push() |
| Home → Profile | push() |
| Product Details → Product List | pop() |
| Settings → Previous Screen | pop() |
| Login → Home without returning to Login | pushReplacement() |
| Checkout complete → Home and clear history | pushAndRemoveUntil() |
20. Common Mistakes
Mistake 1: Forgetting MaterialPageRoute
Navigator.push(
context,
const SecondScreen(),
);
The second argument of Navigator.push() should be a Route. Use MaterialPageRoute or another appropriate route implementation.
Correct Code
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
Mistake 2: Calling pop without the correct context
Make sure the BuildContext passed to the Navigator refers to the intended Navigator.
Mistake 3: Popping when there is no previous route
If necessary, check:
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
Mistake 4: Using push when replacement is required
If the previous screen should not remain in the navigation history, consider pushReplacement() instead of repeatedly pushing new routes.
21. Best Practices
- Use
Navigator.push() when you need to open a new screen.
- Use
Navigator.pop() when returning to the previous screen.
- Use
MaterialPageRoute for Material-style route navigation.
- Use
CupertinoPageRoute when Cupertino-style navigation behavior is appropriate.
- Pass screen data through constructors when the destination screen needs specific data.
- Use
Navigator.pop(context, result) when a destination needs to return data.
- Use
Navigator.canPop(context) when a pop operation may not always be valid.
- Use
pushReplacement() when the current route should be replaced rather than kept in history.
- Use
pushAndRemoveUntil() when previous navigation history needs to be cleared according to a condition.
- For applications with advanced deep-linking and routing requirements, consider Flutter's Router system or a routing package such as
go_router.
22. Navigator Push and Pop Complete Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Push and Pop Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
},
child: const Text('Open Details'),
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Welcome to the Details Screen',
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
),
],
),
),
);
}
}
23. Step-by-Step Execution of the Complete Example
main() starts the Flutter application.
HomeScreen is displayed first.
- The user taps Open Details.
Navigator.push() creates and pushes a MaterialPageRoute.
DetailsScreen is displayed.
- The user taps Go Back.
Navigator.pop(context) removes the Details route.
- The user returns to
HomeScreen.
24. Push and Pop Quick Revision
| Concept | Meaning | Example |
|---|
| Navigator | Manages the navigation stack | Navigator |
| Route | Represents a screen/page in navigation | MaterialPageRoute |
| push() | Adds a new route | Navigator.push(...) |
| pop() | Removes the current route | Navigator.pop(context) |
| canPop() | Checks whether the current route can be popped | Navigator.canPop(context) |
| pushReplacement() | Replaces the current route | Navigator.pushReplacement(...) |
| pushAndRemoveUntil() | Pushes a route and removes routes according to a condition | Navigator.pushAndRemoveUntil(...) |
25. Interview Questions
Q1. What is Navigator in Flutter?
Navigator is a Flutter widget that manages a stack of routes and provides methods for navigating between screens.
Q2. What does Navigator.push() do?
Navigator.push() adds a new route to the Navigator stack and displays the new screen.
Q3. What does Navigator.pop() do?
Navigator.pop() removes the current route from the Navigator stack and returns to the previous route.
Q4. What is MaterialPageRoute?
MaterialPageRoute is a Route implementation used for Material-style screen transitions.
Q5. How can you pass data back to the previous screen?
Use Navigator.pop(context, result) on the destination screen and await the result of Navigator.push() on the previous screen.
Q6. What is the difference between push() and pop()?
push() adds a new route to the stack, while pop() removes the current route from the stack.
Q7. What is pushReplacement()?
pushReplacement() replaces the current route with a new route instead of keeping the current route underneath it.
26. Practice Exercises
- Create a Home screen and Profile screen using
Navigator.push() and Navigator.pop().
- Create a Product List screen and Product Details screen.
- Pass a product name from the List screen to the Details screen.
- Add a custom back button using
Navigator.pop(context).
- Create a Settings screen and return to the Home screen using
Navigator.pop().
- Create a selection screen that returns a selected value to the previous screen.
- Create Login and Home screens and use
pushReplacement() after login.
- Create a checkout flow and use
pushAndRemoveUntil() after checkout completion.
27. Key Takeaways
- Flutter uses routes to represent application screens.
Navigator manages a stack of routes.
Navigator.push() opens a new screen by adding a route to the stack.
Navigator.pop() closes the current screen by removing the top route.
MaterialPageRoute can be used for Material-style navigation.
Navigator.pop(context, result) can return data to the previous screen.
Navigator.canPop(context) can be used to check whether a route can be popped.
pushReplacement() replaces the current route.
pushAndRemoveUntil() can be used to clear routes according to a condition.
- For advanced navigation and deep-linking requirements, Flutter also provides Router-based navigation and supports routing packages such as
go_router.
28. Official Flutter Documentation
29. Flutter Training Resources
Learn more about Flutter development and training through the following resources: