Skip to content

Navigator & Routes

Navigator is a widget that manages a stack of routes. A route is a screen. The route on top of the stack is what the user sees. Navigation is just two stack operations: push a new route on top, or pop the top route off to reveal the one beneath.

flowchart TB
  subgraph stack["route stack"]
    top["DetailScreen  (visible, top)"]
    mid["ListScreen"]
    bottom["HomeScreen  (bottom)"]
  end
  top -->|pop| mid
  mid -->|push DetailScreen| top
Navigator is a stack of routes

The hardware/software back button, the app-bar back arrow, and a swipe-back gesture all do the same thing: pop the top route.

You get the Navigator from the current BuildContext with Navigator.of(context), then push a route. A MaterialPageRoute gives you the platform-correct transition animation for free.

// Push a new screen onto the stack.
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const DetailScreen()),
);
// Pop back to the previous screen.
Navigator.of(context).pop();

The builder is a function that returns the screen widget — it runs when the route is created, not when you call push.

The cleanest way to send data to the next screen is a plain constructor argument. No magic, no global state — just pass it in.

Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DetailScreen(userId: 42),
),
);
// DetailScreen receives it like any widget:
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key, required this.userId});
final int userId;
// ...
}

Navigator.push returns a Future that completes when the pushed route is popped — so you can await a result the user chose on the next screen (a selection, a confirmation).

// Caller awaits the result of the pushed screen.
final picked = await Navigator.of(context).push<Color>(
MaterialPageRoute(builder: (context) => const ColorPicker()),
);
if (picked != null) {
// use the returned Color
}
// Inside ColorPicker, return a value by passing it to pop:
Navigator.of(context).pop(Colors.blue);

For a small app you can register named routes in MaterialApp and navigate by string name. It centralizes the route table, but note: named routes get clumsy with typed arguments and deep links, which is exactly why the ecosystem moved to declarative routing (next lesson).

MaterialApp(
routes: {
'/': (context) => const HomeScreen(),
'/settings': (context) => const SettingsScreen(),
},
);
Navigator.of(context).pushNamed('/settings');
What data structure does Navigator manage?
What is the recommended way to pass data to the next screen?
How do you get a value back from a screen the user interacts with?
Why did the ecosystem move away from named routes for larger apps?