Why does Flutter offer the declarative Router API (MaterialApp.router) alongside Navigator.push, and when does an app actually need it?
answer
- the stack as a function of state
- a deep link should rebuild, not push
- browser back and forward on the web
- pages list instead of push calls
- most apps get it through go_router
basics
~20 sThe Router API derives the navigator's stack from app state: a URL is parsed into state, state builds Navigator.pages, and state is reported back as a URL. Apps need it for deep links that rebuild the stack and web history.
solid answer
~50 s`Navigator.push` is imperative: each call adds one route, and the stack is whatever history of calls happened. That breaks down in two places. A deep link should show a specific stack, say docs home with the layout article on top, regardless of what was open, but imperative or named-route navigation can only push. And on the web the address bar and the browser's back and forward buttons need the app's location as data. The Router API, used through `MaterialApp.router`, fixes both: a `RouteInformationParser` turns a URL into a typed configuration, a `RouterDelegate` holds the state and builds a `Navigator` whose `pages` list is derived from it, and the delegate's `currentConfiguration` is turned back into a URL for browser history. Most teams use it through go_router rather than writing a delegate; plain `Navigator.push` remains fine for mobile-only apps without deep links.
code
dart · 34 linesimport 'package:flutter/material.dart';
// Imperative: the stack is the history of calls.
void openLayoutImperatively(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute<void>(builder: (context) => const ArticleScreen(slug: 'layout')),
);
}
// Declarative: the stack is derived from state.
List<Page<void>> pagesFor(String? openSlug) => [
const MaterialPage<void>(key: ValueKey<String>('home'), child: DocsHome()),
if (openSlug != null)
MaterialPage<void>(
key: ValueKey<String>('doc:$openSlug'),
child: ArticleScreen(slug: openSlug),
),
];
class DocsHome extends StatelessWidget {
const DocsHome({super.key});
@override
Widget build(BuildContext context) => const Scaffold(body: Center(child: Text('Docs')));
}
class ArticleScreen extends StatelessWidget {
const ArticleScreen({super.key, required this.slug});
final String slug;
@override
Widget build(BuildContext context) => Scaffold(body: Center(child: Text(slug)));
}go deeper
Recall that Navigator.push is imperative and the Router API builds the stack from state, which is what deep links and the web need.
Explain the parts, parser, delegate, provider, back-button dispatcher, and how a URL becomes pages and pages become a URL.
Show how pageless routes and reverse-chronological browser history behave in a Router app and how that shapes which screens get URLs.
Decide when a hand-written delegate is justified over go_router, weighing control against maintenance for a web-facing product.
## Two ways to describe navigation Flutter has two navigation styles that share one `Navigator` widget: - **Imperative** (often called Navigator 1.0): code calls `Navigator.push` and `Navigator.pop`. The stack is the accumulated result of those calls. - **Declarative** (the **Router API**, often called Navigator 2.0): the app holds navigation **state**, and the stack is rebuilt from it as a list of `Page` objects passed to `Navigator.pages`, the same way a widget tree is rebuilt from state. The Router API is enabled by `MaterialApp.router` (or `CupertinoApp.router`, `WidgetsApp.router`), given a `routerConfig` or its parts directly. ## Problems the imperative style cannot solve 1. **Deep links that describe a stack.** A link to `/docs/layout` in a documentation app should show docs home with the layout article on top. Flutter's docs note that with named routes a deep link always pushes a new route regardless of where the user is, and that behaviour cannot be customized. 2. **Web history.** In a browser, users expect the address bar to show where they are and the back and forward buttons to move through that history. That requires the app to **report** its location after every change and to **accept** a location from the browser at any time. 3. **Navigation driven by state.** When signing out should remove every signed-in screen, it is simpler to derive the stack from "is the user signed in" than to issue the right sequence of pops. ## The moving parts | Part | Job | |---|---| | `RouteInformationProvider` | Supplies the current `RouteInformation` (a `uri` plus optional `state`) from the platform, e.g. the browser URL; defaults to `PlatformRouteInformationProvider` | | `RouteInformationParser<T>` | Turns `RouteInformation` into a typed configuration `T`, and back again via `restoreRouteInformation` | | `RouterDelegate<T>` | Holds navigation state, applies configurations in `setNewRoutePath`, builds the `Navigator`, and exposes `currentConfiguration` | | `BackButtonDispatcher` | Delivers the Android back button to the delegate's `popRoute` | | `RouterConfig<T>` | Bundles the four so they can be passed as one `routerConfig` | ## The loop in a documentation app 1. The user opens `https://example.dev/docs/layout` in a browser. 2. The provider hands `RouteInformation(uri: /docs/layout)` to the parser, which returns `ArticlePath('layout')`. 3. The delegate's `setNewRoutePath` stores `slug = 'layout'`; the Router rebuilds. 4. The delegate's `build` returns a `Navigator` with pages `[home, article(layout)]`. 5. When the user opens another article in-app, the delegate updates its state and calls `notifyListeners`; the Router asks `currentConfiguration`, the parser's `restoreRouteInformation` turns it into `/docs/animation`, and the browser gets a new history entry. 6. Pressing the browser's back button delivers `/docs/layout` again through the provider, and step 2 repeats. ## When you do not need it - A mobile-only app with no deep links, or deep links that just open one screen: `Navigator.push` with `MaterialPageRoute` is simpler and fully supported. - Result-returning screens (a picker, a confirmation) still use `push` and `pop` inside a Router-based app; those routes are **pageless**: they are not in the URL and are removed when the page beneath them is removed. ## Imperative vs declarative at a glance | Question | `Navigator.push` | Router API | |---|---|---| | Where is the stack defined? | in the sequence of calls made | in state, rebuilt as `pages` | | What does a deep link do? | pushes one more route | replaces the stack with what the URL describes | | Does the web address bar follow? | no | yes, via `currentConfiguration` and the parser | | Result-returning screens | natural: `await push` | still done with `push` on top of pages | | Boilerplate | minimal | parser, delegate, page list (or a package) | ## How teams use it in practice Writing a `RouterDelegate` and parser by hand is verbose, so Flutter's docs recommend a routing package such as **go_router**, which implements the Router API for you from a route table. Knowing the underlying parts still matters: it explains why pushed dialogs are not deep-linkable, why browser back is "reverse chronological" (it returns to the previous location the Router reported, even after in-app pops), and what a custom delegate must implement when a package does not fit.
- Why is a dialog shown with showDialog not deep-linkable in a Router-based app?It is added as a pageless route, not as a `Page` in the delegate's `pages` list, so it is not part of the delegate's state or `currentConfiguration`. The URL cannot describe it, and when the page beneath it is removed, the pageless route is removed too.
- What does 'reverse chronological' browser back mean for a Router app?The browser's back button returns to the previous location the Router reported, not to the route below in the navigator. If the user pops the article in-app and then presses browser back, the article location is delivered again and the article reappears on the stack.
A map with a 'you are here' pin versus a trail of footprints: Navigator.push leaves footprints you can only retrace, while the Router keeps a pin (the state) that anyone, including the browser, can move, and the map (the page stack) is redrawn from it.
saying these in an interview costs you the question
- Thinks the Router API replaced Navigator.push entirely
- Says named routes already handle deep links properly
- Believes the Router is only relevant to web builds
- Thinks go_router is unrelated to the Router API
- Expects dialogs pushed with showDialog to appear in the URL