Navigation and Routes
Moving between screens with Navigator, passing data forward, and returning results back.
2 min de lectura
Every app with more than one screen needs a way to move between them. Flutter models this as a stack of routes, managed by a Navigator — pushing a new screen adds it to the top of the stack, and popping removes it, revealing what was underneath.
Pushing a new screen
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailScreen()),
);
},
child: const Text('View details'),
)MaterialPageRoute wraps your screen widget with the platform-appropriate transition animation (a slide-in on iOS, a fade/scale on Android). Navigator.push takes the current context because, like Theme.of(context) in an earlier lesson, it needs to find the nearest Navigator above this widget in the tree.
Going back is simpler:
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Go back'),
)Passing data to a screen
Data flows forward the same way any Dart constructor takes arguments — there's no special "route parameters" API to learn:
class DetailScreen extends StatelessWidget {
final String productId;
const DetailScreen({super.key, required this.productId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product $productId')),
body: Center(child: Text('Details for $productId')),
);
}
}
// Navigating to it:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(productId: 'sku-123')),
);Getting a result back
Navigator.push returns a Future that completes with whatever value the pushed screen pops with — this is how a screen (say, a color picker or a confirmation dialog) can hand a result back to whoever opened it:
final selectedColor = await Navigator.push<Color>(
context,
MaterialPageRoute(builder: (context) => const ColorPickerScreen()),
);
if (selectedColor != null) {
setState(() => _favoriteColor = selectedColor);
}// Inside ColorPickerScreen, when the user taps a color:
Navigator.pop(context, Colors.blue);The generic type on Navigator.push<Color> and the value passed to pop need to line up — this pattern is the standard way to get a value out of a screen without reaching for shared state just to pass one piece of data back.
Named routes for larger apps
For apps with many screens, defining named routes up front keeps navigation calls shorter and centralizes the map of route names to screens:
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/settings': (context) => const SettingsScreen(),
},
)Navigator.pushNamed(context, '/settings');Named routes work well until a screen needs typed arguments passed in, at which point many teams switch to a dedicated routing package (such as go_router) that adds type-safe route parameters and deep-linking support on top of the same underlying Navigator. Navigator.push with MaterialPageRoute remains the right default for smaller apps and is worth understanding well before reaching for a routing package.