Reader Stacks

Flutter Navigation: Passing Parameters, Refreshing on Back, and Router Events

Passing data between routes, refreshing a page after popping back to it, and subscribing to navigation events all build on the same Navigator API.

Flutter Navigation: Passing Parameters, Refreshing on Back, and Router Events

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.