Skip to content

Instantly share code, notes, and snippets.

@fredgrott
Created May 4, 2025 18:17
Show Gist options
  • Save fredgrott/294bf9d2d5f967abb2c9c4eb55cd564b to your computer and use it in GitHub Desktop.
Save fredgrott/294bf9d2d5f967abb2c9c4eb55cd564b to your computer and use it in GitHub Desktop.
app lifecycle mixin
// Copyright 2025 Fredrick Allan Grott. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:lifecycle/lifecycle.dart';
import 'package:userinterface/core/lifecycle/app_lifecycle_globals.dart';
/// Subscribes lifecycle events for normal widgets.
/// This is used to wrap the scaffold of the app so that
/// we have non-null appView and appDisplay globals.
///
/// One still puts the defaultLifecycleObserver from the
/// Lifecycle package in the navigationObservers slot
/// of the MaterialApp.route constructor.
mixin AppLifecycleMixin<T extends StatefulWidget> on State<T>, LifecycleAware {
LifecycleObserver? _lifecycleObserver;
WidgetDispatchLifecycleMixin? _widgetDispatchLifecycleMixin;
@override
void initState() {
super.initState();
// Dispatches [LifecycleEvent.push] event.
handleLifecycleEvents([LifecycleEvent.push]);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ModalRoute? route = ModalRoute.of(context);
// Avoids re-subscribe when a route is popping.
// When called Navigator#pop(), the [_RouteLifecycle] will change to [popping],
// then notify the [NavigatorObserver].
//
// 如果当前route正在popping,避免重复订阅。
if (route == null || !route.isActive) return;
// grab appView
appView = View.maybeOf(context);
// 重新查找 [WidgetDispatchLifecycleMixin]。
_widgetDispatchLifecycleMixin = null;
context.visitAncestorElements((element) {
if (element is StatefulElement && element.state is WidgetDispatchLifecycleMixin) {
_widgetDispatchLifecycleMixin = element.state as WidgetDispatchLifecycleMixin;
return false;
}
return true;
});
// 优先从 [WidgetDispatchLifecycleMixin] 中订阅。
if (_widgetDispatchLifecycleMixin != null) {
_widgetDispatchLifecycleMixin!.subscribe(this);
} else {
_lifecycleObserver = LifecycleObserver.internalGet(context);
_lifecycleObserver!.subscribe(this, route);
}
}
// To prevent letter-boxing on fodlables, see
// https://docs.flutter.dev/ui/adaptive-responsive/large-screens
void didChangeMetrics() {
// so now we can use appDisplay to get devicePixelRatio and size
// without using MediaQueryData.fromView( FlutterView view)
// This will prevent letter-boxing with foldables.
appDisplay = appView?.display;
}
@override
void dispose() {
// Dispatches [LifecycleEvent.pop].
handleLifecycleEvents([LifecycleEvent.pop]);
_lifecycleObserver?.unsubscribe(this);
_widgetDispatchLifecycleMixin?.unsubscribe(this);
super.dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment