Moving Between Application Screens in Flutter
Moving between application screens is a fundamental part of Flutter development. Real-world applications usually contain multiple screens such as Login, Home, Profile, Products, Product Details, Cart, Settings, and Checkout.
Flutter provides navigation and routing features that allow users to move between these screens. In Flutter, screens and pages are commonly represented as routes. The Navigator manages these routes as a stack, allowing an application to move forward to a new screen and backward to a previous screen.
1. What Does Moving Between Screens Mean?
Moving between screens means changing the currently visible screen when a user performs an action such as tapping a button, selecting a list item, logging in, or opening a product.
For example, a shopping application may have the following flow:
Home Screen
|
v
Product List
|
v
Product Details
|
v
Cart
|
v
Checkout
Flutter navigation allows the user to move forward through this flow and return to previously visited screens.
2. What is a Route?
In Flutter, a screen or page is commonly represented as a route. A route is usually created from a widget.
For example:
HomeScreen
ProfileScreen
SettingsScreen
ProductScreen
ProductDetailsScreen
These widgets can be displayed as routes during application navigation.
3. What is Navigator?
The Navigator manages a stack of routes. When a user moves to another screen, a new route is generally added to the stack. When the user goes back, the current route is removed from the stack.
Navigation Stack Example
Initial:
[Home]
Open Products:
[Home, Products]
Open Details:
[Home, Products, Details]
Open Cart:
[Home, Products, Details, Cart]
Press Back:
[Home, Products, Details]
Press Back:
[Home, Products]
This stack-based approach allows Flutter to maintain navigation history.
4. Basic Navigation Flow
The basic screen navigation flow consists of three important steps:
- Create the screens that need to be displayed.
- Use
Navigator.push() to move to another screen.
- Use
Navigator.pop() to return to the previous screen.
Screen A
|
| Navigator.push()
v
Screen B
|
| Navigator.pop()
v
Screen A
5. Using Navigator.push()
Navigator.push() adds a new route to the navigation stack and displays it.
Syntax
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
Important Parts
| Part | Description |
Navigator.push() | Adds a new route to the navigation stack. |
context | Provides the current widget's location in the widget tree. |
MaterialPageRoute | Creates a Material-style route. |
builder | Builds the destination screen. |
6. Simple Example of Moving to Another 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: 'Screen Navigation',
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: const Center(
child: Text(
'Welcome to Details Screen',
style: TextStyle(fontSize: 22),
),
),
);
}
}
How the Code Works
- The application starts with
HomeScreen.
- The user presses the
Open Details button.
Navigator.push() is called.
- A
MaterialPageRoute is created.
DetailsScreen is displayed.
- The new route is placed above the Home route in the navigation stack.
7. Returning to the Previous Screen with Navigator.pop()
Navigator.pop() removes the current route from the navigation stack and displays the previous route.
Syntax
Navigator.pop(context);
Example
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
)
If the stack contains:
[HomeScreen, DetailsScreen]
after calling Navigator.pop(context) it becomes:
[HomeScreen]
8. Complete Push and Pop Example
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
),
);
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
},
child: const Text('Open Profile'),
),
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Home'),
),
),
);
}
}
9. Navigator.of(context)
Navigation can also be performed using Navigator.of(context).
Push
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
Pop
Navigator.of(context).pop();
Both Navigator.push() and Navigator.of(context).push() can be used for imperative navigation.
10. Moving Between Multiple Application Screens
Applications commonly have more than two screens. Each new navigation action can add another route to the stack.
Home
|
| push
v
Products
|
| push
v
Product Details
|
| push
v
Cart
|
| push
v
Checkout
The user can move backward through the stack:
Checkout
|
| pop
v
Cart
|
| pop
v
Product Details
|
| pop
v
Products
|
| pop
v
Home
11. Practical Three-Screen 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,
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductScreen(),
),
);
},
child: const Text('Open Products'),
),
),
);
}
}
class ProductScreen extends StatelessWidget {
const ProductScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(),
),
);
},
child: const Text('View Product'),
),
),
);
}
}
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('Go Back'),
),
),
);
}
}
12. Navigation Using ListTile
List items are frequently used to open other application screens.
ListTile(
leading: const Icon(Icons.person),
title: const Text('Profile'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
},
)
Multiple Navigation Options
ListView(
children: [
ListTile(
leading: const Icon(Icons.person),
title: const Text('Profile'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
},
),
],
)
13. Navigation Using Buttons
Buttons are another common way of moving between screens.
ElevatedButton
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
},
child: const Text('Open Profile'),
)
TextButton
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
},
child: const Text('Open Settings'),
)
IconButton
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
},
)
14. Navigation from an AppBar
The AppBar can contain navigation actions such as a back button or an icon that opens another screen.
Custom Back Button
AppBar(
title: const Text('Profile'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
)
For standard Material routes, Flutter can automatically display a back button when there is a previous route in the navigation stack.
15. Passing Data While Moving Between Screens
Applications often need to send information from one screen to another. For example, when a user selects a product, the Product Details screen needs to receive information about that product.
Product Model
class Product {
final String name;
final double price;
const Product({
required this.name,
required this.price,
});
}
Product Details Screen
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: Center(
child: Text(
'Price: ₹${product.price}',
style: const TextStyle(fontSize: 22),
),
),
);
}
}
Passing Product Information
final product = Product(
name: 'Flutter Course',
price: 4999,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProductDetailsScreen(product: product),
),
);
This approach passes the selected product directly to the destination screen through its constructor.
16. Returning Data from Another Screen
A destination screen can return a result to the screen that opened it. This is useful for selections, forms, filters, and other user interactions.
Return Data
Navigator.pop(context, 'Flutter');
Receive Data
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (result != null) {
print(result);
}
Complete Example
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
Future openSelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Selected: $result'),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () => openSelection(context),
child: const Text('Choose Option'),
),
),
);
}
}
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');
},
child: const Text('Select Flutter'),
),
),
);
}
}
17. Navigator.pushReplacement()
Navigator.pushReplacement() replaces the current route with a new route.
Syntax
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
Login Example
void login(BuildContext context) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
}
A common navigation flow is:
Login Screen
|
| Login Successful
v
Home Screen
This approach is useful when the current screen should be replaced instead of remaining as the previous route.
18. Navigator.pushAndRemoveUntil()
pushAndRemoveUntil() adds a new route and removes previous routes until a specified condition is satisfied.
Example
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
(route) => false,
);
Using (route) => false removes the previous routes from the navigation stack.
Example Use Case
Login
|
v
Home
|
v
Products
After successful authentication, an application may navigate to Home and remove the Login route from the navigation history.
19. Navigator.popUntil()
Navigator.popUntil() removes routes from the stack until a specified condition becomes true.
Example
Navigator.popUntil(
context,
(route) => route.isFirst,
);
This returns the user to the first route in the navigation stack.
Example
[Home, Products, Details, Cart]
After:
Navigator.popUntil(
context,
(route) => route.isFirst,
);
The stack becomes:
[Home]
20. MaterialPageRoute
MaterialPageRoute creates a route suitable for Material applications and provides a Material-style transition.
Example
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
The destination widget is returned from the builder.
21. CupertinoPageRoute
For Cupertino-style applications, Flutter provides CupertinoPageRoute.
Example
import 'package:flutter/cupertino.dart';
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const DetailsScreen(),
),
);
This route provides a Cupertino-style transition.
22. Named Routes
Flutter supports named routes, where routes are identified using names such as /, /profile, and /settings.
Configuration
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/profile': (context) => const ProfileScreen(),
'/settings': (context) => const SettingsScreen(),
},
)
Navigate Using a Named Route
Navigator.pushNamed(
context,
'/profile',
);
Return
Navigator.pop(context);
Named routes are still supported, but current Flutter documentation does not recommend them for most applications. For straightforward navigation, Navigator with MaterialPageRoute can be used. Applications with more advanced routing and deep-linking requirements can use a routing package such as go_router.
23. Navigation with go_router
For applications that require structured routing, deep linking, nested navigation, or web URL synchronization, a routing package such as go_router can be considered.
Install go_router
flutter pub add go_router
Basic Router Configuration
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileScreen(),
),
],
);
void main() {
runApp(
MaterialApp.router(
routerConfig: router,
),
);
}
Navigate to Profile
ElevatedButton(
onPressed: () {
context.go('/profile');
},
child: const Text('Open Profile'),
)
Go Back
context.pop();
24. Deep Linking
Deep linking allows a URL or external link to open a specific location inside an application.
Example URL
https://example.com/products/25
A routing configuration can use this path to open a specific Product Details screen.
Flutter supports navigation and deep linking across supported platforms. Applications with advanced deep-linking requirements can use Router-based navigation or a routing package such as go_router.
25. Navigation Methods Comparison
| Method | Purpose |
Navigator.push() | Opens a new screen by adding a route to the stack. |
Navigator.pop() | Returns to the previous screen by removing the current route. |
Navigator.pushReplacement() | Replaces the current route with a new route. |
Navigator.pushAndRemoveUntil() | Adds a route and removes previous routes according to a condition. |
Navigator.popUntil() | Removes routes until a specified condition is met. |
Navigator.pushNamed() | Navigates to a configured named route. |
26. Complete Example: Login to Home to Profile
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,
home: const LoginScreen(),
);
}
}
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
void login(BuildContext context) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: Center(
child: ElevatedButton(
onPressed: () => login(context),
child: const Text('Login'),
),
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(),
),
);
},
),
],
),
body: const Center(
child: Text(
'Welcome Home',
style: TextStyle(fontSize: 24),
),
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Home'),
),
),
);
}
}
Flow
Login
|
| pushReplacement
v
Home
|
| push
v
Profile
|
| pop
v
Home
27. Common Navigation Mistakes
Mistake 1: Passing a Widget Directly to Navigator.push()
Navigator.push(
context,
const DetailsScreen(),
);
This is incorrect because Navigator.push() expects a Route.
Correct Version
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
Mistake 2: Creating Unnecessary Navigation History
Repeatedly pushing the same screen can create an unnecessarily large navigation stack. Use the appropriate navigation method for the intended flow.
Mistake 3: Passing Data Incorrectly
Make sure the destination screen receives the correct type and required values when data is passed through a constructor.
Mistake 4: Using Named Routes for Every Application
Named routes are supported, but Flutter's current documentation recommends considering Navigator with MaterialPageRoute or a routing package such as go_router depending on the application's routing requirements.
28. Best Practices
- Use
Navigator.push() for straightforward screen-to-screen navigation.
- Use
Navigator.pop() to return to the previous screen.
- Use
pushReplacement() when the current route should be replaced.
- Use
pushAndRemoveUntil() when previous navigation history needs to be removed.
- Pass screen-specific data explicitly through constructors when practical.
- Use typed results when returning data from another screen.
- Keep navigation flows predictable and easy to understand.
- For complex routing requirements, consider a structured routing solution.
- Test navigation flows on every platform supported by the application.
29. Practice Exercise
Create a Flutter application with the following screen structure:
Home
|
+----> Profile
|
+----> Products
| |
| +----> Product Details
|
+----> Settings
Requirements
- Create a Home Screen.
- Add a Profile navigation button.
- Add a Products navigation button.
- Create a Product List screen.
- Open Product Details when a product is selected.
- Pass product information to Product Details.
- Create a Settings screen.
- Use
Navigator.pop() to return to previous screens.
- Use
pushReplacement() in a Login-to-Home flow.
- Experiment with
pushAndRemoveUntil().
30. Interview Questions
Q1. How do you move from one screen to another in Flutter?
Use Navigator.push() with a route such as MaterialPageRoute.
Q2. How do you return to the previous screen?
Use Navigator.pop(context).
Q3. What is a route?
A route represents a screen or page that can be displayed by the application.
Q4. What is Navigator?
Navigator manages a stack of routes and provides methods for moving between them.
Q5. What does Navigator.push() do?
It adds a new route to the navigation stack and displays it.
Q6. What does Navigator.pop() do?
It removes the current route and returns to the previous route.
Q7. How can data be passed between screens?
Data can be passed through constructor parameters or through route arguments in suitable routing configurations.
Q8. How can a screen return data?
Use Navigator.pop(context, result) and await the result from Navigator.push().
Q9. What is pushReplacement()?
It replaces the current route with a new route.
Q10. When can go_router be useful?
It can be useful for applications with structured routing, advanced navigation, deep linking, or web URL synchronization requirements.
31. Quick Revision Table
| Task | Flutter Code/Approach |
| Open another screen | Navigator.push() |
| Go back | Navigator.pop() |
| Replace current screen | Navigator.pushReplacement() |
| Remove previous routes | Navigator.pushAndRemoveUntil() |
| Return to first route | Navigator.popUntil() |
| Pass data | Constructor parameters |
| Return data | Navigator.pop(context, result) |
| Material-style route | MaterialPageRoute |
| Cupertino-style route | CupertinoPageRoute |
| Advanced routing | Router or routing package such as go_router |
32. Key Takeaways
- Flutter applications commonly contain multiple screens.
- Flutter screens and pages are represented as routes.
Navigator manages a stack of routes.
Navigator.push() moves to a new screen.
Navigator.pop() returns to the previous screen.
MaterialPageRoute can be used for Material-style navigation.
CupertinoPageRoute can be used for Cupertino-style navigation.
- Data can be passed between screens using constructor parameters.
- A screen can return data using
Navigator.pop().
pushReplacement() replaces the current route.
pushAndRemoveUntil() can remove previous routes from the stack.
- Advanced applications can use Router-based navigation or a routing package such as
go_router.
33. Official Flutter Documentation
34. JustAcademy Flutter Training Resources
For additional Flutter learning resources and course information, visit the following links: