| name | route_observer_mixin-route-awareness |
| description | Use when tracking Flutter screen navigation lifecycle events (screen focus, push, pop, backgrounding) using route_observer_mixin without boilerplate subscription management. |
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
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
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().