Irbisa · Cheatsheet

Navigation & Routing

Navigator, named routes, go_router, passing data between screens

Junior Developer · 24 questions

  1. 01

    What is the Navigator, what is a route, and how do push and pop work?

    Navigator is a widget that manages a stack of Route objects: push puts a new screen on top, pop removes the top one and shows the screen underneath again.

    // Open a screen on top of the current oneNavigator.of(context).push(  MaterialPageRoute(builder: (_) => const DetailsPage()),); // Close the top screen…
  2. 02

    What is the difference between push, pushReplacement, pushAndRemoveUntil and popUntil, and which one do you use after login?

    push adds a route on top, pushReplacement swaps the top route, pushAndRemoveUntil pushes and drops routes below until a predicate says stop, and popUntil pops back to a route already on the stack.

    // Swap only the top routeNavigator.pushReplacement(  context,  MaterialPageRoute(builder: (_) => const HomePage()),);…
  3. 03

    How do you pass data to a new screen and get a result back?

    Pass data forward through the new screen's constructor, and send a result back by popping with a value: push returns a Future that completes with whatever the second screen passes to pop.

    // Forward: constructorNavigator.push(  context,  MaterialPageRoute(builder: (_) => DetailsPage(userId: '123')),);…
  4. 04

    ColorPickerPage pops with Navigator.pop(context, 'red'), but the user leaves with the system back button. What does this code print?

    Future<void> pickColor(BuildContext context) async {  final color = await Navigator.push<String>(    context,    MaterialPageRoute(builder: (_) => const ColorPickerPage()),  );  print('picked: $color');}

    It prints picked: null.

  5. 05

    How do named routes work, and how do you handle a parameterised route like /product/42 with onGenerateRoute?

    MaterialApp.routes maps exact names to screens; any name it does not match goes to onGenerateRoute, where you parse the name yourself and return a Route.

    MaterialApp(  routes: {    '/': (_) => const HomePage(),    '/settings': (_) => const SettingsPage(),  },  onGenerateRoute: (settings) {…
  6. 06

    Tapping this button throws "Navigator operation requested with a context that does not include a Navigator": why?

    class App extends StatelessWidget {  const App({super.key});   @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        body: ElevatedButton(          onPressed: () => Navigator.of(context).push(            MaterialPageRoute(builder: (_) => const NextPage()),          ),          child: const Text('Go'),        ),      ),    );  }}

    The context belongs to App, which sits above MaterialApp, and MaterialApp creates the Navigator inside itself.

  7. 07

    How do showDialog, showModalBottomSheet and showMenu fit into the navigation stack?

    All three push a modal route onto a Navigator, so what looks like an overlay is a real entry on the stack: back closes it first, and it returns whatever you pass to Navigator.pop.

    final confirmed = await showDialog<bool>(  context: context,  barrierDismissible: false,  builder: (dialogContext) => AlertDialog(    title: const Text('Delete account?'),    actions: […
  8. 08

    How do you intercept the back button to confirm leaving a screen with unsaved changes (PopScope, formerly WillPopScope)?

    Wrap the screen in PopScope with canPop: false while there are unsaved changes; the pop is then refused, onPopInvokedWithResult runs with didPop == false, and you ask the user and pop yourself if they confirm.

    class _EditProfileState extends State<EditProfilePage> {  bool _dirty = false;   @override  Widget build(BuildContext context) {    return PopScope(…
  9. 09

    What is Navigator 2.0 (the Router API), how does it differ from Navigator 1.0, and why was it introduced?

    Navigator 1.0 is imperative: you call push and pop.

    class _AppState extends State<App> {  Order? _selected; // the stack is a function of this state   @override  Widget build(BuildContext context) {    return Navigator(…
  10. 10

    In go_router, what is the difference between context.go() and context.push()?

    go replaces the whole stack with the one your route table implies for that location; push puts one page on top of what is already there.

    final router = GoRouter(  routes: [    GoRoute(      path: '/orders',      builder: (context, state) => const OrdersPage(),      routes: […
  11. 11

    How do you build bottom navigation where each tab keeps its state and its own back stack?

    Give each tab its own Navigator and keep all tabs alive: with go_router that is StatefulShellRoute.indexedStack, which keeps one branch per tab and switches between them without disposing anything.

    final router = GoRouter(  initialLocation: '/home',  routes: [    StatefulShellRoute.indexedStack(      builder: (context, state, shell) => Scaffold(        body: shell, // keeps every branch alive…
  12. 12

    Find the bug: after OrderPage moved into a bottom-nav tab, tapping No closes the order screen and leaves this dialog open.

    Future<bool?> confirmCancel(BuildContext context) => showDialog<bool>(      context: context,      builder: (_) => AlertDialog(        title: const Text('Cancel this order?'),        actions: [          TextButton(            onPressed: () => Navigator.pop(context, false),            child: const Text('No'),          ),          TextButton(            onPressed: () => Navigator.pop(context, true),            child: const Text('Yes'),          ),        ],      ),    );

    The buttons pop with the page's context, not the dialog's.

  13. 13

    How do you guard routes for authentication with go_router?

    Use redirect: go_router calls it before showing any location, and you return a different path to send the user elsewhere or null to let them through.

    class AuthController extends ChangeNotifier {  AuthStatus status = AuthStatus.loading; // loading, signedIn, signedOut  void update(AuthStatus s) { status = s; notifyListeners(); }} GoRouter buildRouter(AuthController auth) => GoRouter(…
  14. 14

    Find the bug: after switching to dark mode on the settings screen, the app jumps back to the home screen.

    class App extends StatelessWidget {  const App({super.key, required this.theme});  final ValueNotifier<ThemeMode> theme;   @override  Widget build(BuildContext context) => ValueListenableBuilder(        valueListenable: theme,        builder: (context, mode, _) => MaterialApp.router(          themeMode: mode,          routerConfig: GoRouter(routes: [            GoRoute(path: '/', builder: (_, __) => const HomePage()),            GoRoute(path: '/settings', builder: (_, __) => const SettingsPage()),          ]),        ),      );}

    A new GoRouter is created inside build, so every rebuild of App (here a theme change) hands MaterialApp.router a fresh router that starts at its initial location, and the whole navigation stack is lost.

  15. 15

    What is the stack after this code runs, which screen is on top, and what does the next Back show?

    // the stack starts as [Home]final nav = Navigator.of(context);nav.pushNamed('/a');nav.pushNamed('/b');nav.pushNamed('/c');nav.pushReplacementNamed('/d');nav.pop();

    The stack is [Home, A, B] and B is on top: pushReplacement swapped C for D, so the final pop removed D and uncovered B.

  16. 16

    How do you set up deep links, Android App Links and iOS Universal Links in a Flutter app?

    Use verified https links: host assetlinks.json and apple-app-site-association under /.well-known/ on your domain, declare the domain in the app, and let MaterialApp.router receive the path; Flutter passes deep links to the router by default since 3.27.

    <!-- android/app/src/main/AndroidManifest.xml, inside the main activity --><intent-filter android:autoVerify="true">  <action android:name="android.intent.action.VIEW" />  <category android:name="android.intent.category.DEFAULT" />  <category android:name="android.intent.category.BROWSABLE" />  <data…
  17. 17

    A push notification is tapped and you must open a specific screen. How do you navigate with no BuildContext at hand?

    Navigate through an object that lives outside the widget tree: a GlobalKey<NavigatorState> passed to MaterialApp.navigatorKey, or with go_router the GoRouter instance itself, kept in your DI container.

    final navigatorKey = GlobalKey<NavigatorState>(); MaterialApp(navigatorKey: navigatorKey, home: const HomePage()); // From a service: no context neededvoid openOrder(String id) {…
  18. 18

    Your Flutter web app shows URLs like example.com/#/orders/42, and after switching to clean paths a page refresh returns 404: what is going on?

    Flutter web uses the hash strategy by default: everything after # is never sent to the server, so any static host works.

  19. 19

    How do you build a custom page transition, and what are animation and secondaryAnimation?

    Use PageRouteBuilder (or CustomTransitionPage in go_router) and wrap the page in your own transition inside transitionsBuilder; animation drives this route in and out, secondaryAnimation drives it while another route is pushed on top of it.

    Route<T> fadeScaleRoute<T>(Widget page) {  return PageRouteBuilder<T>(    transitionDuration: const Duration(milliseconds: 280),    reverseTransitionDuration: const Duration(milliseconds: 220),    pageBuilder: (context, animation, secondaryAnimation) => page,    transitionsBuilder: (context, animation, secondaryAnimation, child) {…
  20. 20

    How do Hero animations work, and what are the common pitfalls?

    Wrap a widget in Hero with the same tag on both screens; during the page transition Flutter lifts it into the overlay and animates it from its old position and size to the new one.

    // List screenListTile(  leading: Hero(    tag: 'product-${product.id}', // unique per item    child: Image.network(product.thumbUrl, width: 56, height: 56),  ),…
  21. 21

    How does a screen find out that another route was pushed on top of it, for example, to pause a video?

    Use a RouteObserver and mix RouteAware into the screen's State: didPushNext fires when a route covers this one, didPopNext when it is visible again.

    final routeObserver = RouteObserver<PageRoute<void>>();// MaterialApp(navigatorObservers: [routeObserver], ...) class _PlayerPageState extends State<PlayerPage> with RouteAware {  late final VideoPlayerController _controller = widget.controller;…
  22. 22

    On a slow phone a user double-taps a list row and two identical detail screens land on the stack: how do you prevent it?

    Guard the navigation yourself: the Navigator never deduplicates, because two routes built from the same widget really are two routes.

    class _OrderListState extends State<OrderList> {  bool _navigating = false;   Future<void> _open(Order order) async {    if (_navigating) return; // second tap is ignored    _navigating = true;…
  23. 23

    A deep link to a product opens the product screen, but Back closes the app instead of going to the catalog. How do you rebuild a proper back stack?

    Make the URL describe the hierarchy and let the router build every page above the target: in go_router, nest the product route under the catalog route, so /catalog/product/42 produces [Home, Catalog, Product] and Back walks up instead of out.

    final router = GoRouter(  routes: [    GoRoute(      path: '/',      builder: (_, __) => const HomePage(),      routes: […
  24. 24

    Android kills your app in the background, and when the user returns the three-screen-deep stack is gone. How do you restore it?

    Turn on Flutter's state restoration: give the app a restorationScopeId and push with the restorable* Navigator methods, so the framework saves the route stack with the OS and rebuilds it after the process is killed.

    void main() => runApp(const MaterialApp(      restorationScopeId: 'app', // without this nothing is restored      home: OrderListPage(),    )); // Top-level and annotated: saved as a reference plus its arguments…