skills/flutter-navigation/references/navigation-patterns.md

3.8 KiB

Navigation Patterns

Use this reference to choose a navigation approach before editing app code.

Approach Selection

Navigator With MaterialPageRoute

Use for simple, local flows that do not need to be addressable by URL:

  • opening a detail screen from a list in a small mobile app;
  • selecting a value and returning it to the previous screen;
  • modal-like flows that should not survive browser refresh or external links.

Example: assets/navigator_basic.dart

final result = await Navigator.of(context).push<String>(
  MaterialPageRoute<String>(
    builder: (context) => const SelectionScreen(),
  ),
);
if (!context.mounted) return;

go_router

Use for route tables that need stable URLs or controlled page stacks:

  • web apps with browser back, forward, refresh, and direct links;
  • Android App Links, iOS Universal Links, or custom schemes;
  • auth, onboarding, or feature redirects;
  • nested navigation with persistent app shell;
  • multiple Navigator branches;
  • scalable route names and generated locations.

Example: assets/go_router_basic.dart

Legacy MaterialApp.routes Named Routes

Avoid adding new legacy named routes for most apps. They can handle simple static route names, but incoming deep links always push a new route and browser forward support is limited. Preserve them only when maintaining a small existing app with no custom deep-link or web-history requirements.

Data Passing

Constructor Data With Navigator

Use direct constructor arguments for local, in-memory data:

Navigator.push(
  context,
  MaterialPageRoute<void>(
    builder: (context) => DetailScreen(item: item),
  ),
);

Example: assets/passing_data.dart

Path Parameters With go_router

Use path parameters for required identity:

GoRoute(
  path: '/users/:userId',
  builder: (context, state) {
    final userId = state.pathParameters['userId']!;
    return UserScreen(userId: userId);
  },
);

context.go('/users/42');

Query Parameters With go_router

Use query parameters for optional URL state:

final location = Uri(
  path: '/search',
  queryParameters: {'q': 'flutter', 'tab': 'docs'},
).toString();

context.go(location);

Read them from state.uri.queryParameters:

final query = state.uri.queryParameters['q'] ?? '';

Extra Data With go_router

Use extra only for data that is intentionally not addressable:

context.push('/details', extra: item);

Do not rely on extra for browser refresh, shared URLs, or native deep-link entry.

Returning Data

Navigator:

final result = await Navigator.push<String>(
  context,
  MaterialPageRoute<String>(builder: (context) => const SelectionScreen()),
);
if (!context.mounted) return;

go_router:

final result = await context.push<String>('/selection');
if (!context.mounted) return;

Return from the pushed route:

context.pop('selected-value');

Example: assets/returning_data.dart

Deep Linking Behavior

Requirement Prefer
Direct URL opens a predictable page stack go_router or Router API
App replaces current pages when a link is opened go_router or Router API
Browser forward/back support go_router or Router API
Very simple mobile-only stack Navigator
Static legacy app route names Existing MaterialApp.routes, with limitations

When Router and imperative Navigator are mixed, pages pushed imperatively are not deep-linkable and can be removed when the parent Router-backed page changes.

Web-Specific Choices

Hash strategy works without server changes:

https://example.com/#/details/42

Path strategy needs server rewrites but gives cleaner URLs:

https://example.com/details/42

See web-navigation.md for setup and validation.