Kodokon kodokon.com

Navigating between screens with Navigator

Push and pop screens with Navigator.push and pop, pass data through constructors, and retrieve a result when the screen closes.

8 min · 3 questions

Open this lesson in Kodokon

Flutter manages your screens as a stack: Navigator.push places a new screen on top, Navigator.pop removes the top one and reveals the previous one. Each screen is a route, and MaterialPageRoute provides the platform's native transition. Android's back button calls pop for you, without a single line of code.

DART
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: FilledButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) {
                  return const DetailScreen(
                    productName: 'Headphones',
                  );
                },
              ),
            );
          },
          child: const Text('View product'),
        ),
      ),
    );
  }
}
push pushes DetailScreen (defined below) on top of the current screen.

To pass data to the next screen, there is no magic mechanism: the destination screen is a widget like any other, and it receives its data through its constructor. It's typed, explicit, and the compiler catches you if a field is missing. Note that the AppBar of the pushed screen shows the back button without you writing anything.

DART
import 'package:flutter/material.dart';

class DetailScreen extends StatelessWidget {
  const DetailScreen({
    super.key,
    required this.productName,
  });

  final String productName;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(productName)),
      body: Center(
        child: FilledButton(
          onPressed: () {
            Navigator.pop(context, true);
          },
          child: const Text('Add to cart'),
        ),
      ),
    );
  }
}
pop closes the screen and returns a value to the caller.

Navigator.push returns a Future that completes when the screen is closed, with the value passed to pop. You can therefore await the result of a screen: a confirmation, an item chosen from a list, a filled-in form. The generic type push<bool> documents what you expect to get back.

DART
import 'package:flutter/material.dart';

Future<void> openDetail(BuildContext context) async {
  final added = await Navigator.push<bool>(
    context,
    MaterialPageRoute<bool>(
      builder: (context) {
        return const DetailScreen(
          productName: 'Headphones',
        );
      },
    ),
  );
  if (!context.mounted) return;
  final message = (added ?? false)
      ? 'Product added to cart'
      : 'No product added';
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text(message)),
  );
}
push's Future completes when the screen closes.

Knowledge check

Make sure you remember the key points of this lesson.

  1. How does Navigator organize screens?
    • As a queue: first in, first shown
    • As a stack: the last screen pushed is shown on top
    • As a parallel widget tree
    • As a grid of screens
  2. What is the idiomatic way to pass data to the next screen?
    • Through a global variable
    • Via the destination widget's constructor
    • By writing it to disk
    • Navigator doesn't allow passing data
  3. What does Navigator.pop(context, true) do?
    • It opens a new screen named "true"
    • It closes all screens in the stack
    • It closes the current screen and returns true to the awaiting push