Skip to content

Intermediate · Lists, navigation and forms · topic 24 of 50 · 30 min

Navigator basics

Explanation

Navigator manages a stack of screens (routes). Navigator.push(context, MaterialPageRoute(builder: ...)) opens a new screen on top; Navigator.pop(context) goes back. Pass data forward through the new screen's constructor, and return a result by awaiting push and passing a value to pop. pushReplacement swaps the current screen, for example after login, and PopScope controls the back button.

Screens as a stack

In Flutter, a screen is called a route. The Navigator keeps routes in a stack: opening a screen pushes a route on top; going back pops it off and shows the one below. The system back button and the AppBar's back arrow both pop the top route.

output
Start:           [ Products ]
Tap a product:   [ Products, Details(42) ]
Tap "Buy":       [ Products, Details(42), Checkout ]
Back:            [ Products, Details(42) ]
push adds to the top; pop removes the top.

Each route is usually a full screen: a widget with its own Scaffold. Scaffold's AppBar automatically shows a back arrow when there is a route below it to return to, so you rarely need to write a back button yourself.

Push and pop

dart
// From the products screen:
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => ProductDetails(productId: 42)),
);

// Inside ProductDetails, to go back:
Navigator.pop(context);
MaterialPageRoute gives the platform-style transition animation.

Passing data forward

The simplest and safest way to send data to a new screen is through its constructor, like any widget. Pass small things such as an id; the new screen can load the full details itself.

dart
class ProductDetails extends StatelessWidget {
  const ProductDetails({super.key, required this.productId});
  final int productId;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Product $productId')),
      body: Center(
        child: FilledButton(
          onPressed: () => Navigator.pop(context, 'added'),   // return a result
          child: const Text('Add to cart'),
        ),
      ),
    );
  }
}
pop can carry a result back to the screen that pushed this one.

Getting a result back

dart
Future<void> openDetails(BuildContext context) async {
  final result = await Navigator.push<String>(
    context,
    MaterialPageRoute(builder: (_) => const ProductDetails(productId: 42)),
  );
  if (!context.mounted) return;               // the screen may be gone after the await
  if (result == 'added') {
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Added to cart')));
  }
}
push returns a Future that completes when the pushed route is popped. result is null if the user pressed back.

Replacing and clearing

MethodEffectTypical use
pushAdd a screen on topOpen details
popRemove the top screen, optionally with a resultBack, Cancel, Save and close
pushReplacementReplace the current screenAfter login: replace Login with Home
pushAndRemoveUntilPush and remove screens below until a conditionAfter logout or order success: clear the stack

Controlling the back button

Sometimes Back should ask first, for example on a half-filled form. Current Flutter versions use the PopScope widget (which replaced the older WillPopScope): set canPop to false while there are unsaved changes, and react in its callback by showing a "Discard changes?" dialog. Check the current API in the docs, as its callback name has changed between versions.

Think of it like this: The Navigator is like a stack of plates in a canteen. Each new screen is a plate placed on top; Back takes the top plate away. pushReplacement swaps the top plate for a new one, so you cannot go back to the old one.

Common mistakes

  • Using the context of a screen after an await without checking context.mounted.
  • Leaving the login screen in the stack, so Back after login returns to it. Use pushReplacement.
  • Passing big objects or global variables instead of constructor arguments or ids.
  • Calling Navigator.pop on the first screen, which closes the app on Android.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Navigator basics · 1/6

Navigator = stack of routes

Screens are routes in a stack managed by the Navigator.

Prefer a video by a teacher? Find free videos on “flutter navigator push pop pass data return result pushreplacement” on YouTube ↗

In real life: Choosing a delivery address

At checkout, a food delivery app pushes an "Select address" screen. When the user taps an address, that screen pops with the chosen address as the result. The checkout screen awaits the push, checks context.mounted, and shows the chosen address. If the user presses back instead, the result is null and nothing changes.

dart
Future<void> pickAddress(BuildContext context) async {
  final address = await Navigator.push<String>(
    context,
    MaterialPageRoute(builder: (_) => const AddressPicker()),
  );
  if (address != null && context.mounted) {
    // update state with the chosen address, for example via setState in a StatefulWidget
  }
}

// In AddressPicker: ListTile(title: Text(a), onTap: () => Navigator.pop(context, a))
Results flow back through pop.

Practice (0/3 done)

Task 1 · Question

The stack is [Home, List, Details]. The user presses Back twice. Which screen is visible?

Task 2 · Question

What does await Navigator.push(...) return if the user presses the back button?

Task 3 · Practice

Build a three-screen flow: Login (pushReplacement to Home), Home with a list of items, and Details that receives an item through its constructor and returns "liked" or nothing via pop. Home shows a SnackBar when an item was liked. Check that Back from Home closes the app instead of returning to Login.

Quick quiz

Question 1

1.What does Navigator.pop(context) do?

Question 2

2.Which is the recommended way to pass a product id to a details screen with Navigator.push?

Question 3

3.Which method replaces the current screen so Back cannot return to it?

Question 4

4.Which widget controls whether the back button can leave a screen in current Flutter?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does Navigator.pop(context) do?

Answer: Removes the top screen from the stack

Back to the previous route.

Which is the recommended way to pass a product id to a details screen with Navigator.push?

Answer: The details screen's constructor

Constructor arguments are explicit and type-safe.

Which method replaces the current screen so Back cannot return to it?

Answer: pushReplacement

Used after login.

Which widget controls whether the back button can leave a screen in current Flutter?

Answer: PopScope

PopScope replaced WillPopScope.

What is a widget?Related

Answer: An immutable description of part of the UI

Flutter rebuilds the screen from widgets.

From Everything is a widget

Which MaterialApp constructor works with go_router?Related

Answer: MaterialApp.router()

Pass routerConfig: router.

From Routing with go_router

What is the time complexity of push and pop?Related

Answer: O(1)

Only the top is touched.

From Stacks

Why pass lambdas like onProductClick instead of the NavController to screens?Related

Answer: Screens stay reusable, previewable and testable

Screens should not depend on navigation details.

From Navigation Compose

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Lists, navigation and forms” before its exam: Routing with go_router, Forms and validation.
  2. Module 6 exam: Lists, navigation and forms · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, State management. 50 topics left in the course before the final exam.

← Previous: Grids with GridView