Skip to content
Boring Doc
Boring Doc explanation Verified against Flutter 3.47.7 3 min readnavigationroutingNavigatorpushpop

Navigation and routing

Routes, stacks, deep links and the back button.

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. After any await on navigation, check mounted (or context.mounted) before touching the BuildContext; the user may have left the screen meanwhile.
Sequence diagrampush, await, pop with a result

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.

Where the analogy breaks: A pile of cards has no address. Deep links and browser URLs need a way to say 'show me exactly these cards in this order', which the imperative push/pop API cannot express; that is why Router describes the pile as a list of Pages instead. The analogy also hides that cards underneath are kept alive (their State persists) but do not rebuild on their own when the pile changes.

Minimal working example

Push a detail screen with data, then pop back
dart
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'),
        ),
      ),
    );
  }
}
Expected: Tapping the tile slides in DetailPage titled 'Dash'. The Back button, the app bar arrow or the system back gesture return to the list, whose State was never destroyed.Flutter ≥ 3.0.0Navigate to a new screen and back (cookbook)
  1. L13Navigator.push looks up the nearest Navigator above this context (MaterialApp creates one) and pushes the route onto its stack.
  2. L15MaterialPageRoute provides the Material page transition; the type argument is the type of result the route can pop with (none here).
  3. L16Data travels forward through an ordinary constructor argument. The builder runs lazily when the route is first shown.
  4. 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.

✗ No mounted check
dart
Future<void> _pick(BuildContext context) async {
  final result = await Navigator.push<String>(context, _route());
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$result')));
}
✓ Guarded with context.mounted
dart
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.

✗ Context from above MaterialApp
dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: ElevatedButton(
      onPressed: () => Navigator.push(context, _route()), // wrong context
      child: const Text('Open'),
    ),
  );
}
✓ Builder supplies a context under the Navigator
dart
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.

  • Navigation and routing overview Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/navigation
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Navigate to a new screen and back Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/cookbook/navigation/navigation-basics
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Send data to a new screen Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/cookbook/navigation/passing-data
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Return data from a screen Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/cookbook/navigation/returning-data
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Navigate with named routes Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/cookbook/navigation/named-routes
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Pass arguments to a named route Flutter 3.47.7 Explanation verified against current source
    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
  • Deep linking Flutter 3.47.7 Explanation verified against current source
    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 source
    https://docs.flutter.dev/learn/pathway/tutorial/navigation
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • https://api.flutter.dev/flutter/widgets/Navigator-class.html
    Last checked
    Oct 21, 2026
Was this useful?
AI explanationAI-generated · grounded in cited sources
AI explanations are not configured
Set 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.