Passing parameters to a new route, reacting when the user navigates back to a previous screen, and subscribing to navigation events more generally are all built on Flutter's core Navigator API.
Passing parameters to a new route
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(productId: 42),
),
);
class ProductDetailPage extends StatelessWidget {
final int productId;
const ProductDetailPage({required this.productId, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(body: Text('Product #$productId'));
}
}
Passing data as a constructor argument to the destination widget is the most direct approach for simple values — for a full named-route setup, arguments are passed through RouteSettings instead.
Passing parameters through named routes
Navigator.pushNamed(context, '/product-detail', arguments: {'productId': 42});
final args = ModalRoute.of(context)!.settings.arguments as Map;
final productId = args['productId'];
Getting a result back when a route is popped
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => EditProductPage(product: product)),
);
if (result == true) {
// the edit page popped with `true`, meaning something was actually saved — refresh
setState(() {
_refreshProducts();
});
}
// in EditProductPage, after saving
Navigator.pop(context, true);
Navigator.push returns a Future that resolves with whatever value the pushed route passes to Navigator.pop() — this is the standard pattern for refreshing a list page after an edit page pops back to it, without needing a separate state management solution just for this one interaction.
Subscribing to navigation events more broadly with a NavigatorObserver
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPop(Route route, Route? previousRoute) {
print('Popped: ${route.settings.name}');
}
@override
void didPush(Route route, Route? previousRoute) {
print('Pushed: ${route.settings.name}');
}
}
MaterialApp(
navigatorObservers: [MyNavigatorObserver()],
// ...
)
A NavigatorObserver is the right tool when you need to react to navigation events globally (for analytics/screen-view tracking, for instance) rather than just handling the result of one specific push/pop pair — it sees every navigation event across the whole app, not just the ones you explicitly awaited.
Refreshing a page specifically when returning to it via a route observer
class _MyPageState extends State with RouteAware {
@override
void didPopNext() {
// called when returning to this route from one pushed on top of it
_refreshData();
}
}
RouteAware's didPopNext() fires specifically when a route above the current one is popped and this page becomes visible again — a more targeted alternative to the awaited-Future pattern above, useful when the page needs to refresh regardless of what specific value (if any) the popped route returned.