# Route Observer Mixin Route Awareness

> Use when tracking Flutter screen navigation lifecycle events (screen focus, push, pop, backgrounding) using route_observer_mixin without boilerplate subscription management.

- Skill: `mono0926/route-observer-mixin-route-awareness` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mono0926/route-observer-mixin-route-awareness`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mono0926/route-observer-mixin-route-awareness/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: mono0926 (https://skillmd.com/u/mono0926)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mono0926/route-observer-mixin-route-awareness

---


# route_observer_mixin Screen Tracking Guide

`route_observer_mixin` eliminates manual registration and disposal of Flutter `RouteObserver` and `RouteAware` subscriptions. It provides clean, declarative hooks for page lifecycle transitions (`didPush`, `didPop`, `didPushNext`, `didPopNext`).

## Guidelines

- **Setup in Navigator**:
  - Wrap your app with `RouteObserverProvider` above `MaterialApp` (or provide it via `provider` / `riverpod`).
  - Pass `RouteObserverProvider.of(context)` (or the provider's instance) to `MaterialApp.navigatorObservers`.
- **Implementing in State**:
  - Add `with RouteAware, RouteObserverMixin` to the target `State<MyWidget>` class.
  - Implement lifecycle methods:
    - `didPush()`: Called when the screen has been pushed and is now visible.
    - `didPopNext()`: Called when the top route has been popped off, and this screen becomes visible again.
    - `didPushNext()`: Called when a new route is pushed on top of this screen (this screen becomes obscured).
    - `didPop()`: Called when this screen has been popped off the navigator.
- **Analytics & Tracking**:
  - Track screen views inside `didPush()` and `didPopNext()` to accurately capture when the user returns to a previously viewed page.
- **Lifecycle Cleanup**:
  - Do not manually call `routeObserver.unsubscribe(this)`. `RouteObserverMixin` automatically manages subscription registration in `didChangeDependencies` and unsubscription in `dispose`.

## Examples

### 1. App Setup

```dart
import 'package:flutter/material.dart';
import 'package:route_observer_mixin/route_observer_mixin.dart';

void main() {
  runApp(
    RouteObserverProvider(
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [
        RouteObserverProvider.of(context),
      ],
      home: const HomeScreen(),
    );
  }
}
```

### 2. Page Implementation with Analytics Tracking

```dart
import 'package:flutter/material.dart';
import 'package:route_observer_mixin/route_observer_mixin.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen>
    with RouteAware, RouteObserverMixin {

  @override
  void didPush() {
    super.didPush();
    // Screen is pushed for the first time
    _trackScreenView();
  }

  @override
  void didPopNext() {
    super.didPopNext();
    // User returned back to this screen from a pushed page
    _trackScreenView();
    _refreshContent();
  }

  @override
  void didPushNext() {
    super.didPushNext();
    // Another screen was pushed on top of this screen
    _pauseMediaPlayback();
  }

  void _trackScreenView() {
    debugPrint('Analytics: Screen viewed -> HomeScreen');
  }

  void _refreshContent() {
    debugPrint('Refreshing active screen data');
  }

  void _pauseMediaPlayback() {
    debugPrint('Pausing background video/audio');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Navigator.of(context).push(
            MaterialPageRoute(builder: (_) => const DetailScreen()),
          ),
          child: const Text('Go to Details'),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text('Details')),
      );
}
```

## Common Pitfalls & Anti-Patterns

- ❌ **Anti-pattern**: Manually managing `RouteObserver().subscribe(this, ModalRoute.of(context)!)` inside `didChangeDependencies`, which frequently leaks subscriptions if unsubscription is missed in `dispose`.
  - ✔️ **Correct**: Use `RouteObserverMixin`, which handles registration and unsubscription safely and deterministically.
- ❌ **Anti-pattern**: Only tracking screen view analytics in `initState`, which fails to capture when users navigate back to the page via the back button.
  - ✔️ **Correct**: Track screen views in both `didPush()` and `didPopNext()`.

