A screen can become covered by another route while the application remains in the foreground. This earlier mixin used route observation to name that narrower activation boundary.
The route-aware mixin
import 'package:flutter/material.dart';
import 'package:nft/my_app.dart';
import 'package:nft/utils/app_log.dart';
mixin RouteActiveMixin<T extends StatefulWidget> on State<T>
implements RouteAware {
bool _subscribed = false;
bool active = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_subscribed) {
MyApp.routeObserver.subscribe(this, ModalRoute.of(context));
_subscribed = true;
}
}
@override
void dispose() {
if (_subscribed) {
MyApp.routeObserver.unsubscribe(this);
_subscribed = false;
}
super.dispose();
}
@override
void didPush() => onActive();
@override
void didPopNext() => onActive();
@override
void didPop() => onInactive();
@override
void didPushNext() => onInactive();
/// Update active flag
void onActive() {
active = true;
logger.d(
'${runtimeType.toString()} onActive ${ModalRoute.of(context).settings.name}');
}
/// Update inactive flag
void onInactive() {
active = false;
logger.d(
'${runtimeType.toString()} onInactive ${ModalRoute.of(context).settings.name}');
}
}
Connect the observer and state
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
static final GlobalKey<NavigatorState> _navigatorKey =
GlobalKey<NavigatorState>();
static final RouteObserver<Route<dynamic>> routeObserver =
RouteObserver<Route<dynamic>>();
static NavigatorState get navigator => _navigatorKey.currentState;
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: MyApp._navigatorKey,
debugShowCheckedModeBanner: false,
initialRoute: AppConstant.rootPageRoute,
onGenerateRoute: AppRoute.generateRoute,
navigatorObservers: <NavigatorObserver>[MyApp.routeObserver],
);
}
}
class _HomePageState extends State<HomePage>
with RouteActiveMixin<HomePage> {
@override
Widget build(BuildContext context) {
return const ScreenWidget(
body: HomeScreenBody(),
);
}
}
The observer instance used by the navigator must match the one the route-aware state subscribes to. Subscription and unsubscription belong to the state’s lifetime.
Keep route names available
import 'package:flutter/material.dart';
import 'package:nft/pages/base/content_page.dart';
import 'package:nft/pages/counter/counter_page.dart';
import 'package:nft/pages/home/home_page.dart';
import 'package:nft/utils/app_constant.dart';
class AppRoute {
/// Generate route for app here
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case AppConstant.counterPageRoute:
return MaterialPageRoute<dynamic>(
settings: settings,
builder: (_) => ContentPage(
body: CounterPage(argument: settings.arguments as String)));
case AppConstant.rootPageRoute:
default:
return MaterialPageRoute<dynamic>(
settings: settings,
builder: (_) => const ContentPage(body: HomePage()));
}
}
}
- Start app
flutter: [D] _HomePageState onActive /
- Navigate to Counter Screen
flutter: [D] _HomePageState onInactive /
flutter: [D] _CounterPageState onActive /counter_screen
- Navigate back to Home by pop
flutter: [D] _HomePageState onActive /
flutter: [D] _CounterPageState onInactive /counter_screen
The output is an earlier trace, not newly collected runtime evidence. Route settings, nested navigators, and replacement/removal operations can change which callbacks apply. Source: original template. No route interaction was executed for this revision.