Why does the Flutter documentation discourage named routes for most apps, and what does onGenerateRoute add over the MaterialApp routes table?
answer
- not deprecated, just limited
- deep links always push a new route
- no browser forward button
- arguments are an untyped Object?
- home, routes, onGenerateRoute, onUnknownRoute
basics
~20 sNamed 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 sNamed 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 linesimport '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
Recall that named routes still work but the Flutter docs recommend go_router or MaterialPageRoute instead for most apps.
Explain the documented limitations, the lookup order from home to onUnknownRoute, and what onGenerateRoute adds for arguments and typing.
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.
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