skip to content

Why does the Flutter documentation discourage named routes for most apps, and what does onGenerateRoute add over the MaterialApp routes table?

level: middleimportance: should knowfreq 42%

answer

  1. not deprecated, just limited
  2. deep links always push a new route
  3. no browser forward button
  4. arguments are an untyped Object?
  5. home, routes, onGenerateRoute, onUnknownRoute

basics

~20 s

Named routes always push a new route for a deep link and lack browser forward support, so the docs suggest go_router or MaterialPageRoute. onGenerateRoute builds routes in code, so it can type results and parse arguments.

solid answer

~40 s

Named routes, `MaterialApp.routes` plus `Navigator.pushNamed`, still work and are not deprecated, but Flutter's navigation docs don't recommend them for most apps: when a deep link arrives, Flutter always pushes a new route regardless of where the user is, the behaviour can't be customized, and the browser forward button isn't supported. The docs point to a routing package such as go_router, or plain `Navigator` with `MaterialPageRoute`. The routes table is also weakly typed: arguments travel as `Object?` and every entry is built as a `MaterialPageRoute<dynamic>`, so `pushNamed<Address>` against it fails with a cast error. `onGenerateRoute` receives the `RouteSettings` and returns any `Route`, so it can build a `MaterialPageRoute<Address>`, validate arguments and return `null` to fall through to `onUnknownRoute`.

code

dart · 32 lines
dart
import 'package:flutter/material.dart';

Route<dynamic>? generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case '/address-picker':
      return MaterialPageRoute<Address>(
        settings: settings,
        builder: (context) => const AddressPickerScreen(),
      );
  }
  return null; // MaterialApp then calls onUnknownRoute
}

Future<Address?> pickAddress(BuildContext context) =>
    Navigator.pushNamed<Address>(context, '/address-picker');

class Address {
  const Address(this.label);
  final String label;
}

class AddressPickerScreen extends StatelessWidget {
  const AddressPickerScreen({super.key});

  @override
  Widget build(BuildContext context) => Scaffold(
        body: ListTile(
          title: const Text('Home'),
          onTap: () => Navigator.pop(context, const Address('Home')),
        ),
      );
}

go deeper

for a junior

Recall that named routes still work but the Flutter docs recommend go_router or MaterialPageRoute instead for most apps.

for a middle

Explain the documented limitations, the lookup order from home to onUnknownRoute, and what onGenerateRoute adds for arguments and typing.

for a senior

Show why typed results fail with the routes table and how you would migrate a named-route app when deep links or the web become requirements.

for a principal

Weigh the cost of migrating a working named-route app against the product need for deep links, the web and testable navigation.

## What named routes are A **named route** is a screen registered under a string such as `/address-picker`. With the `MaterialApp.routes` table you map names to builders and navigate with `Navigator.pushNamed(context, '/address-picker')`. The app resolves a name in a fixed order: 1. For `/`, the `home` widget, if set. 2. The `routes` table. 3. **`onGenerateRoute`**, a callback that receives `RouteSettings` (name and arguments) and returns a `Route` or `null`. 4. **`onUnknownRoute`**, the fallback when everything else returned `null`. ## Why the docs discourage them Flutter's navigation overview is explicit: named routes are **not recommended for most applications**. Two limitations are named: - **Deep links cannot be customized.** When the platform delivers a deep link, Flutter pushes a new route onto the navigator regardless of where the user currently is. You cannot, for example, rebuild the stack as home then order then tracking. - **No browser forward button.** On the web, named routes do not support forward navigation. The recommendation is a routing package such as go_router, which parses the URL and configures the navigator from it, or plain `Navigator` with `MaterialPageRoute` for apps that do not need URLs. Named routes are **not deprecated**; they are simply the least capable of the three options. ## The typing problems Beyond the documented limitations, the routes table is weakly typed: | Concern | Routes table | `onGenerateRoute` | |---|---|---| | Arguments | `Object?`, read with `ModalRoute.settingsOf(context)?.arguments` and cast | parsed and validated in one place | | Result type | every route built as `MaterialPageRoute<dynamic>` | any `Route<T>` you construct | | Unknown names | error in debug unless `onUnknownRoute` is set | return `null` to fall through | | Guards, redirects | none | any code before returning a route | The result-type row causes a real crash. `Navigator.pushNamed<Address>(context, '/address-picker')` casts the generated route to `Route<Address?>`. If the route came from the routes table it is a `MaterialPageRoute<dynamic>`, which is not a `Route<Address?>`, so the cast throws at runtime. With `onGenerateRoute` you construct `MaterialPageRoute<Address>` and the typed push works. ```dart MaterialApp( home: const CheckoutScreen(), onGenerateRoute: (RouteSettings settings) { switch (settings.name) { case '/address-picker': return MaterialPageRoute<Address>( settings: settings, builder: (context) => const AddressPickerScreen(), ); } return null; // falls through to onUnknownRoute }, onUnknownRoute: (RouteSettings settings) => MaterialPageRoute<void>( settings: settings, builder: (context) => const NotFoundScreen(), ), ); ``` ## What onGenerateRoute does not fix `onGenerateRoute` improves typing and centralizes construction, but it keeps the named-route navigation model: a deep link is still pushed on top of the current stack, and the web forward button is still unsupported. Those limitations come from imperative, name-at-a-time navigation, and only a declarative router (the Router API or go_router on top of it) removes them. ## When named routes are still acceptable - A small mobile-only app with no deep links, where a name table is a convenient index of screens. - Legacy code that works: rewriting it is a cost with no user-visible gain until deep links or the web matter. For new code, prefer a routing package when URLs matter, and `Navigator.push` with `MaterialPageRoute` for simple result-returning screens such as an address picker. ## Migrating off named routes 1. Keep `pushNamed` call sites working while you introduce a router: go_router can take the same path strings, so screens move one at a time. 2. Replace `arguments` casts with typed constructor parameters or path and query parameters. 3. Move result-returning screens (pickers, confirmations) to `Navigator.push` with a typed `MaterialPageRoute`, which works alongside a router. 4. Delete the `routes` table once no name resolves through it, and keep an unknown-route screen in the router. ## Interview framing A strong answer quotes the two documented limitations, says named routes are discouraged rather than deprecated, and adds the typing issue, ideally the `pushNamed<Address>` cast failure, to show hands-on experience.

  • Does onGenerateRoute let named routes handle a deep link by rebuilding the whole stack?
    No. It still produces one route per name, and a deep link is still pushed on top of whatever is showing. Rebuilding the stack from a URL needs the declarative Router API, usually through go_router.
  • How do you read arguments passed with pushNamed?
    The pushed route's `RouteSettings.arguments` holds them as `Object?`. Inside the screen, `ModalRoute.settingsOf(context)?.arguments` returns them and you cast; in `onGenerateRoute` you can read `settings.arguments`, validate the type once and pass a typed value to the screen's constructor instead.

saying these in an interview costs you the question

  • Says named routes were deprecated or removed from Flutter
  • Claims named routes cannot pass arguments at all
  • Expects pushNamed<Address> to work with the routes table
  • Believes onGenerateRoute fixes deep-link stack handling
  • Thinks the routes table is consulted after onGenerateRoute