Navigation & Routing
Almost every real app has more than one screen. Navigation is how Flutter moves the user between screens, and routing is the system that decides which screen to show and how it got there. Flutter models this with a very intuitive metaphor: a stack of screens, called routes, where the screen on top is the one the user sees. Understanding this stack — and how to push data forward onto it and pop results back off it — is the foundation for every multi-screen Flutter app you'll ever build.
The Navigator: A Stack of Screens
Every MaterialApp comes with a built-in Navigator widget that manages a stack of routes. Think of it exactly like a stack of index cards: the card on top is what's currently visible. Opening a new screen pushes a new card onto the stack. Pressing the back button, or calling code that pops, removes the top card and reveals whatever was underneath. This is why the Android back button and the iOS swipe-back gesture "just work" in Flutter apps by default — they're both driven by the same underlying stack.
| Operation | What it does |
|---|---|
Navigator.push | Adds a new screen on top of the stack — the user moves forward |
Navigator.pop | Removes the current screen from the stack — the user moves back |
Navigator.pushNamed | Same as push, but refers to the destination by a route name instead of building the widget directly |
Navigator.pushReplacement | Replaces the current screen with a new one, without leaving the old one on the stack (useful after login screens) |
Pushing a New Screen
The most direct way to move to a new screen is Navigator.push, combined with a
MaterialPageRoute that describes how to build the destination widget and
how it should transition in (a platform-appropriate slide/fade animation, by default). The context
argument is the BuildContext of the widget doing the navigating — Flutter
uses it to find the nearest Navigator in the widget tree.
// Inside a button's onPressed callback on the current screen
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
},
child: const Text('Open Details'),
)
Popping Back
To go back to the previous screen, call Navigator.pop(context). This is usually wired to
an app bar's back button automatically, but you can also trigger it yourself — for example from a
"Done" or "Cancel" button.
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
)
Passing Data To a New Screen
The simplest and most "Dart-native" way to pass data forward is to give the destination widget a constructor parameter, exactly like you would for any other widget. There's no special navigation API for this — it's just normal object construction, which means it's fully type-safe and the compiler will catch mistakes for you.
class DetailsScreen extends StatelessWidget {
final String title;
final int itemId;
const DetailsScreen({super.key, required this.title, required this.itemId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('Item #$itemId')),
);
}
}
// Navigating and passing data in one step
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(title: 'Blue Widget', itemId: 42),
),
);
Returning Data Back From a Screen
Navigation can flow in the other direction too: a screen can send a result back to whoever pushed it,
by passing a value to Navigator.pop(context, result). To receive that value, mark the call
site with await — because Navigator.push itself returns a
Future that only completes once the pushed screen is popped.
// The screen that is popped, sending a value back
ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Apple');
},
child: const Text('Pick Apple'),
)
// The screen that pushed it, waiting for and using the result
Future<void> _selectFruit(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const FruitPickerScreen()),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('You picked: $result')),
);
}
}
result comes back as dynamic (or whatever type you annotate), and will be null if the user backs out of the screen without explicitly popping a value — for example by pressing the device back button. Always check for null before using it.Named Routes
For larger apps, constructing every destination widget by hand at every call site can get repetitive and scattered. Named routes let you register a map of string names to screen-building functions once, on the MaterialApp itself, and then navigate by name from anywhere in the app.
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailsScreen(),
'/settings': (context) => const SettingsScreen(),
},
)
// Navigating from anywhere in the app
Navigator.pushNamed(context, '/details');
The tradeoff is worth understanding clearly. Named routes make it easy to reason about an app's overall
navigation structure at a glance, since every destination is listed in one place. But they're
noticeably more awkward for passing complex, type-safe data — the routes map's builder
functions don't take extra parameters directly, so you typically have to reach for the generic
arguments property on RouteSettings and cast it back to the
right type at the destination, which loses some of the compile-time safety that direct widget
construction gives you for free.
A Brief Word On go_router
As apps grow — especially ones that need deep linking (opening a specific screen directly from a URL or a push notification), nested navigation (like separate tabs that each keep their own back stack), or web-style URL-based routing — many teams reach for the go_router package, which is the approach commonly recommended by the Flutter team itself for this kind of complexity. You don't need it to build the apps in this tutorial, and the built-in Navigator covers everything shown above perfectly well — but it's worth knowing the name so you recognize it when you see it in larger, production Flutter codebases.