Flutter Lesson 33 of 83 3 min read
Navigation in Flutter: Navigator.push and pop
Learn screen navigation in Flutter with Navigator.push, pop, pushReplacement and pushAndRemoveUntil, and how the navigation stack works.
On this page
Flutter keeps screens in a stack, like a pile of cards. Opening a screen pushes it on top. Going back pops it off, revealing the one beneath. A screen is called a route.
Push and pop #
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: FilledButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(builder: (context) => const DetailsPage()),
);
},
child: const Text('Open details'),
),
),
);
}
}
class DetailsPage extends StatelessWidget {
const DetailsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: Center(
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Go back'),
),
),
);
}
}
MaterialPageRoutewraps the new page and gives it the platform’s standard transition.- The
AppBaron the second page shows a back arrow automatically. - The system back button and the iOS back swipe also pop the route.
The stack #
Start: [Home]
push Details: [Home, Details]
push Reviews: [Home, Details, Reviews]
pop: [Home, Details]
pop: [Home]
Replacing instead of stacking #
Sometimes the user should not be able to go back, for example from a login screen once they are signed in.
// Replace the current screen.
Navigator.pushReplacement(
context,
MaterialPageRoute<void>(builder: (context) => const HomePage()),
);
// Clear the whole stack and start again from a new screen.
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute<void>(builder: (context) => const LoginPage()),
(route) => false, // remove every existing route
);
// Pop back to the first screen.
Navigator.popUntil(context, (route) => route.isFirst);
| Method | Stack before | Stack after |
|---|---|---|
push(C) | A, B | A, B, C |
pop() | A, B, C | A, B |
pushReplacement(C) | A, B | A, C |
pushAndRemoveUntil(C, (r) => false) | A, B | C |
popUntil((r) => r.isFirst) | A, B, C | A |
Can we go back? #
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
Navigator.maybePop(context); // pops only if possible
Full-screen dialogs #
A page for creating something often slides up from the bottom and shows a close icon in place of a back arrow.
Navigator.push(
context,
MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (context) => const NewNotePage(),
),
);
Navigating after async work #
After an await, the screen may no longer exist. Check before you use context.
Future<void> _save() async {
await repository.save(note);
if (!mounted) return; // in a State class
Navigator.pop(context);
}
In a StatelessWidget or a helper function, check context.mounted.
Shorter calls #
Navigator.of(context).push(...) and Navigator.push(context, ...) are the same thing. Many teams add an extension:
extension NavigationX on BuildContext {
Future<T?> push<T>(Widget page) =>
Navigator.of(this).push<T>(MaterialPageRoute(builder: (_) => page));
void pop<T>([T? result]) => Navigator.of(this).pop(result);
}
// Usage
context.push(const DetailsPage());
Is this enough? #
Navigator.push is all a small app needs. Once you want web URLs, deep links from notifications, or redirects based on login state, switch to a routing package. See go_router.
Try it yourself #
Build three screens: a list of fruit, a details screen for the fruit that was tapped, and a “thank you” screen reached from details. On the thank-you screen, add a button that returns straight to the list.