## Learning Objectives

By the end of this chapter, you will be able to:

- Understand Flutter's navigation models: imperative Navigator and declarative Router API
- Implement basic push/pop navigation and named routes
- Pass arguments between screens and handle returned results
- Use nested navigation for complex apps (BottomNavigationBar with independent stacks)
- Implement deep links and app links for external navigation into specific screens
- Apply best practices for route organization, testing, and state preservation

1

## Navigation Models Overview

Flutter provides two navigation approaches, each suited for different use cases.

### Imperative API (Navigator)

Simple push/pop stack-based navigation using Navigator.push, Navigator.pop, and named routes via Navigator.pushNamed. Works well for most apps and quick flows.

### Declarative API (Router)

More control and integrates with browser URLs and deep linking. Use when building web apps or complex routing logic (URL-driven state).

Choose Navigator for most mobile apps; adopt Router when you need URL synchronization, nested route parsing, or advanced deep-link handling.

2

## Basic Imperative Navigation with Navigator

The Navigator API provides straightforward methods for moving between screens.

### Navigation Methods

- Push: Push a new route (screen) onto the navigation stack using `Navigator.push(context, MaterialPageRoute(builder: (_) => NewPage()))`.
- Pop: Pop the current route with `Navigator.pop(context)` or by using system back button.
- Replace: Use `Navigator.pushReplacement` to replace the current route or `Navigator.pushAndRemoveUntil` to clear stacks.

### Example Patterns

Push:

    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => DetailsPage(item: item)),
    );

Pop:

    Navigator.pop(context);

Replace:

    Navigator.pushReplacement(
      context,
      MaterialPageRoute(builder: (context) => HomePage()),
    );

3

## Named Routes (Centralized)

Named routes provide a centralized way to manage navigation throughout your app.

### Setting Up Named Routes

Define routes in `MaterialApp(routes: {...})` or use `onGenerateRoute` for dynamic routing. Named routes improve consistency and make deep linking easier.

### Pattern

    MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomePage(),
        '/details': (context) => DetailsPage(),
      },
    );

Navigate:

    Navigator.pushNamed(context, '/details', arguments: {'id': 42});

Handle arguments in target:

    final args = ModalRoute.of(context)!.settings.arguments as Map;
    final id = args['id'];

Guideline: Use `onGenerateRoute` for argument validation and to avoid brittle casting at the call site.

4

## Passing Data and Receiving Results

Flutter provides flexible ways to pass data between screens and receive results.

### Passing Data

Pass data via constructors when pushing:

    Navigator.push(context, MaterialPageRoute(builder: (_) => EditPage(item: item)));

### Receiving Results

Return results using `Navigator.pop(context, result)` and await the push call:

    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => FormPage())
    );
    if (result != null) { /* handle result */ }

Use cases: Form submission, selection dialogs, and confirmation screens.

5

## Route Lifecycle and State Preservation

Understanding route lifecycle helps you manage state and resources effectively.

### State Preservation

- When pushing new routes, previous route states are preserved by default (widgets remain in the tree but may be inactive).
- For heavy stateful screens with controllers, ensure proper disposal in `dispose()` and consider caching strategies if route recreation is expensive.
- Use `PageStorage` and `PageStorageKey` to preserve scroll positions and basic widget state across navigation.

### Example: Preserve ListView Scroll Position

    ListView.builder(
      key: PageStorageKey('courseList'),
      controller: _controller,
      itemCount: ...
    )

6

## Nested Navigation and Independent Stacks

Complex apps often require independent navigation stacks for different sections.

### Useful Pattern

BottomNavigationBar with independent navigation stacks per tab so users can switch tabs and resume interaction where they left off. Implement with an `IndexedStack` to keep tab screens alive and a `Navigator` per tab to manage its own stack.

### Pattern Outline

- Top-level Scaffold with BottomNavigationBar
- Body: IndexedStack of Navigator widgets, each with its own `onGenerateRoute` and `NavigatorKey`

### Key Points

- Use a `GlobalKey<NavigatorState>` per tab to push/pop within that tab.
- Manage back button behavior: if active tab's Navigator can pop, pop it; otherwise, exit app or switch to default tab.

7

## Drawer and Modal Routes

Drawers and modals provide alternative navigation patterns.

### Drawer Navigation

Drawer navigation typically replaces or pushes routes; choose consistent behavior to avoid surprising users.

### Modal Routes

Modal routes (`showDialog`, `showModalBottomSheet`) overlay content; they return results via `Navigator.pop`.

### Example: showDialog Returns Result

    final confirmed = await showDialog(
      context: context,
      builder: (_) => ConfirmDialog()
    );
    if (confirmed == true) { /* user confirmed */ }

8

## Declarative Routing with Router (Brief Guide)

The Router API provides advanced routing capabilities for complex applications.

### When to Use Router

Use Router, RouterDelegate, and RouteInformationParser when building web-first apps or complex URL-driven flows. High-level idea: parse incoming URL into a configuration object, let RouterDelegate build a stack of Pages from that configuration, and update browser history when configuration changes.

### When to Pick Router

- Your app must reflect URL path segments and query parameters.
- You need deep linking integrated with complex nested navigation or restoration via URLs.

Note: Router has a steeper learning curve but offers full control over URL to UI mapping and back stack.

9

## Deep Linking and App Links

Deep links allow external sources to open your app to specific screens.

### Platform Integrations

- Android: Configure intent filters in AndroidManifest.xml (app links for verified domains).
- iOS: Configure Universal Links with Associated Domains and URL types.
- Flutter: Use packages like `uni_links`, `receive_sharing_intent`, or `flutter_branch_io` for richer integrations.

### Flow

- App receives incoming URI string via platform channel or package API.
- Parse the URI into route name and arguments.
- Navigate to the appropriate screen, considering whether the app was cold-started or already running.

### Example Deep Link Handler (Conceptual)

    void handleIncomingUri(Uri uri) {
      if (uri.pathSegments.isEmpty) return;
      switch (uri.pathSegments.first) {
        case 'course':
          final id = uri.queryParameters['id'];
          Navigator.pushNamed(context, '/course', arguments: {'id': id});
          break;
      }
    }

Guideline: Support both initial link handling (on app start) and link stream (while app running).

10

## Handling Edge Cases and Security

Proper error handling and security are essential for robust navigation.

### Best Practices

- Validate all deep-link parameters server-side where necessary; do not trust client-provided IDs for sensitive operations.
- Gracefully handle missing or invalid arguments by showing fallback screens or friendly error messages.
- Avoid pushing duplicate routes for the same content; deduplicate by checking existing route stack or using `pushReplacement` when appropriate.

11

## Testing Navigation

Testing navigation ensures your app's routing works correctly.

### Widget Testing

- Write widget tests that pump the widget and use `tester.tap` and `tester.pumpAndSettle()` to assert navigation outcomes.
- For Router-based apps, test `RouteInformationParser` and `RouterDelegate` in isolation by providing fake route information and verifying built pages.

### Example Widget Test Pattern

Pump the app widget, tap navigation button, await pumpAndSettle, assert new screen text exists using `find.text`.

12

## Best Practices and Organization

Organizing navigation code properly makes maintenance easier.

### Best Practices

- Centralize route names and argument contracts in a single file or enum to avoid string typos.
- Keep navigation triggers in UI layers and navigation logic in coordinators/services when apps grow.
- Prefer passing strongly-typed objects in constructors rather than raw maps when possible; define a `RouteArguments` model to reduce runtime casting errors.
- Document route contracts (expected arguments and types) for team clarity.

### Suggested File Layout

- `lib/routes.dart` — route name constants and helper navigation functions
- `lib/navigation/` — navigator keys, coordinators, and tab navigation helpers
- `lib/screens/` — individual screen widgets (each screen owns its own argument parsing)

13

## Exercises

Practice what you've learned with these exercises:

### 1. Simple push/pop

Implement HomePage with a list of items. On tap, push DetailsPage passing the tapped item. On DetailsPage, include a Delete button that pops with a boolean result; on HomePage, remove the item if result is true.

### 2. Named routes and arguments

Configure named routes and navigate via `Navigator.pushNamed`, passing a typed `CourseArgs` object. On the target page, use ModalRoute to extract and display fields.

### 3. BottomNavigation with independent stacks

Build a 3-tab app (Home, Search, Profile) where each tab maintains its own navigation stack. Implement back button handling so back pops the active tab's stack first.

### 4. Deep-link simulation

Simulate receiving a deep link URI that navigates to a course detail screen. Handle both cold start (app not running) and warm start (app in background) scenarios using a package like `uni_links` (conceptually if environment constraints prevent full integration).

### 5. Router basics (optional advanced)

Implement a simple Router-based app where URL paths `/` and `/profile/:id` map to HomePage and ProfilePage respectively. Verify resizing the browser updates the displayed route if applicable.

14

## Session Assignment

Complete Exercises 1–3 with well-typed argument models and include unit/widget tests for navigation actions. Provide a short README describing how you organized routes, why you chose Navigator vs Router, and how you handled state restoration for each tab.