Navigator keeps a stack of Route objects; Navigator.push adds a route built from a widget (usually via MaterialPageRoute) and Navigator.pop removes the top one, optionally handing a result back to the Future that push returned. For URL-driven apps and deep links, switch to the Router API through a package such as go_router instead of named routes.
- In Flutter a screen is a route: a plain widget wrapped in a Route subclass. MaterialPageRoute and CupertinoPageRoute add the platform's page transition and back gesture.
- Navigator.push(context, route) returns a Future<T?> that completes when that route is popped; Navigator.pop(context, result) completes it with a value, or null when the user uses the back button.
- Pass data forward through the destination widget's constructor. Avoid globals; RouteSettings.arguments plus ModalRoute.of(context) is the alternative when you cannot construct the widget yourself.
- Named routes (MaterialApp.routes, Navigator.pushNamed) deduplicate route creation but are no longer recommended for most apps: deep-link behaviour cannot be customised and the browser forward button does not work.
- Router (MaterialApp.router with go_router or a hand-written RouterDelegate) is declarative: the current URL maps to a list of Pages, which is what deep links and web history need.
- After any await on navigation, check mounted (or context.mounted) before touching the BuildContext; the user may have left the screen meanwhile.
push adds a route and hands back a Future. The Future stays pending until the pushed route is popped, which is how the first screen learns what the user chose.
A stack of index cards on a desk
The Navigator is a desk with a pile of cards, one per screen. Pushing drops a new card on top so it is the only one you see; popping lifts it off and reveals the card underneath exactly as you left it. If you hand someone a card with a question written on it ('which sort order?'), you wait until they lift it off and read the answer they scribbled on the back; that is push returning a Future and pop supplying the result. Named routes are a filing cabinet of pre-printed cards you can fetch by label.
Minimal working example
import 'package:flutter/material.dart';
class ListPage extends StatelessWidget {
const ListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Items')),
body: ListTile(
title: const Text('Dash'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => const DetailPage(name: 'Dash'),
),
);
},
),
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({super.key, required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(name)),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Back'),
),
),
);
}
}- L13Navigator.push looks up the nearest Navigator above this context (MaterialApp creates one) and pushes the route onto its stack.
- L15MaterialPageRoute provides the Material page transition; the type argument is the type of result the route can pop with (none here).
- L16Data travels forward through an ordinary constructor argument. The builder runs lazily when the route is first shown.
- L36pop removes the top route and returns to ListPage. The AppBar's automatic back button and the Android back gesture do the same thing.
Use cases
- Use Navigator.push/pop with MaterialPageRoute (or CupertinoPageRoute) for mobile flows where screens stack: list to detail, settings sub-pages, wizards.
- Use push's returned Future when a screen exists to collect a choice (a picker, a confirmation, a form) and the caller needs the answer.
- Use pushReplacement or pushAndRemoveUntil for login-to-home transitions where the user must not be able to go back.
- Use the Router API via go_router (MaterialApp.router) when the app runs on the web, needs shareable URLs, or must handle deep links that replace the current stack.
Benefits
- The stack matches how users think about 'back', and each platform's transition and back gesture are handled for you.
- Typed results via Future<T?> keep data flow explicit: no shared mutable globals between screens.
- Routes underneath keep their State, so returning to a list restores scroll position and selections without extra work.
Limitations
- The imperative stack has no URL representation; syncing it with browser history or an incoming deep link means rebuilding the stack by hand, which is exactly what Router/go_router automate.
- Routes created with push or showDialog are pageless: with a routing package they are not deep-linkable and are discarded when the page-backed route beneath them is removed.
- Named routes cannot customise what happens when a deep link arrives (a new route is always pushed) and do not support the browser forward button.
Alternatives and trade-offs
- go_router — You want declarative, URL-based routing with nested navigation, redirects and deep links without writing a RouterDelegate.
- Router with a custom RouterDelegate and RouteInformationParser — You need full control over how app state maps to a list of Pages and are willing to own the plumbing.
- NavigationBar / TabBar with IndexedStack — Top-level sections should switch in place rather than stack; combine with Navigator inside each tab if those sections push their own screens.
- Platform integration for app links — The hard part is the Android/iOS configuration that makes the OS open your app for a URL.
Using the BuildContext after awaiting a navigation
Problem: The user can pop or dispose the calling screen while a pushed route is open. Using context (for ScaffoldMessenger, Navigator, setState) once the Future completes then throws or targets a dead element.
Fix: Check `mounted` in a State, or `context.mounted` in a stateless callback, immediately after the await and return early if it is false.
Future<void> _pick(BuildContext context) async {
final result = await Navigator.push<String>(context, _route());
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$result')));
}Future<void> _pick(BuildContext context) async {
final result = await Navigator.push<String>(context, _route());
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$result')));
}Calling Navigator with a context above the Navigator
Problem: The context passed to build in the widget that creates MaterialApp sits above the Navigator, so Navigator.of(context) fails with 'Navigator operation requested with a context that does not include a Navigator'.
Fix: Navigate from a widget below MaterialApp. If the code genuinely lives in the same build method, wrap the caller in a Builder to obtain a child context, or move it into a separate widget.
Widget build(BuildContext context) {
return MaterialApp(
home: ElevatedButton(
onPressed: () => Navigator.push(context, _route()), // wrong context
child: const Text('Open'),
),
);
}Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => ElevatedButton(
onPressed: () => Navigator.push(context, _route()),
child: const Text('Open'),
),
),
);
}Assuming pop always returns a value
Problem: Users dismiss screens with the system back button or a swipe, in which case pop runs with no result and the Future completes with null. Code that force-unwraps the result crashes on the most common exit path.
Fix: Treat the result as nullable: handle null as 'cancelled' and keep the previous state.
Starting with named routes for a web or deep-linked app
Problem: MaterialApp.routes looks convenient, but the navigation overview marks it as not recommended: deep links always push a fresh route regardless of the current stack and the browser forward button is unsupported, so you end up rewriting routing later.
Fix: Use Navigator with MaterialPageRoute for simple mobile stacks, and adopt go_router (or another Router-based package) as soon as URLs, web history or deep links matter.
This page is Boring Doc’s independent explanation. For the complete specification use the official pages below (content © The Flutter Authors, CC BY 3.0; code samples BSD). Dates show when the explanation was last checked and when the official page last changed.
- https://docs.flutter.dev/ui/navigation
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/cookbook/navigation/navigation-basics
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/cookbook/navigation/passing-data
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/cookbook/navigation/returning-data
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/cookbook/navigation/named-routes
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/ui/navigation/deep-linking
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- Stack-based navigation (learning pathway) Flutter 3.47.7 Explanation verified against current sourcehttps://docs.flutter.dev/learn/pathway/tutorial/navigation
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- Navigator class API reference Flutter 3.47.7https://api.flutter.dev/flutter/widgets/Navigator-class.html
- Last checked
- Oct 21, 2026
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.