diff --git a/CHANGELOG.md b/CHANGELOG.md index 0de43e7..235ed01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ +## 4.19.0 +July 10, 2024 +- AppState.detachedAppLifecycleState() 'attempts' to call State object's deactivate() and dispose() +- AppState.didChangeAppLifecycleState() encompasses life cycle event flags. +- Renamed event flags: inactive, hidden, paused, resumed, detached to + _inactiveAppLifecycle, _hiddenAppLifecycle, _pausedAppLifecycle, _resumedAppLifecycle, _detachedAppLifecycle +- Renamed the functions: + inactiveAppLifecycle(), hiddenAppLifecycle(), pausedAppLifecycle(), resumedAppLifecycle(), detachedAppLifecycle() +- Removed @protected from methods to be used outside an instance member's subclasses: + ## 4.18.0 June 15, 2024 - getter usingCupertino assigned a local variable diff --git a/example/integration_test/test_example_app.dart b/example/integration_test/test_example_app.dart index 5b1c15b..7c6d529 100644 --- a/example/integration_test/test_example_app.dart +++ b/example/integration_test/test_example_app.dart @@ -61,7 +61,7 @@ Future integrationTesting(WidgetTester tester) async { } /// This should be the 'latest' State object running in the App - expect(state.isEndState, isTrue, reason: _location); + expect(state.isLastState, isTrue, reason: _location); /// Go to Page 1 await tester.tap(find.byKey(const Key('Page 1'))); @@ -105,26 +105,26 @@ Future integrationTesting(WidgetTester tester) async { expect(event, isTrue, reason: _location); } - event = state.paused; + event = state.pausedAppLifecycle; if (event) { /// The app has been paused expect(event, isTrue, reason: _location); } - event = state.inactive; + event = state.inactiveAppLifecycle; if (event) { expect(event, isTrue, reason: _location); } - event = state.detached; + event = state.detachedAppLifecycle; if (event) { expect(event, isTrue, reason: _location); } - event = state.resumed; + event = state.resumedAppLifecycle; if (event) { expect(event, isTrue, reason: _location); @@ -146,9 +146,9 @@ Future integrationTesting(WidgetTester tester) async { expect(event, isTrue, reason: _location); } - if (!state.hidden) { + if (!state.hiddenAppLifecycle) { // Should not happen, but don't trip it here regardless! gp - expect(state.hidden, isFalse, reason: _location); + expect(state.hiddenAppLifecycle, isFalse, reason: _location); } if (!state.mounted) { @@ -181,11 +181,11 @@ Future integrationTesting(WidgetTester tester) async { await tester.pumpAndSettle(const Duration(milliseconds: 200)); // Test the controllers move across different State objects. - state = con.state!.startState as StateX; + state = con.state!.firstState as StateX; expect(state, isA(), reason: _location); - state = con.state!.endState as StateX; + state = con.state!.lastState as StateX; expect(state, isA(), reason: _location); diff --git a/example/lib/src/controller.dart b/example/lib/src/controller.dart index 6ebef3d..a265ac8 100644 --- a/example/lib/src/controller.dart +++ b/example/lib/src/controller.dart @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'package:example/src/another_app/controller.dart'; +export '/src/another_app/controller.dart'; -export 'package:example/src/controller/app/app_controller.dart'; +export '/src/controller/app/app_controller.dart'; -export 'package:example/src/controller/home/_controller.dart'; +export '/src/controller/app/common/events_controller.dart'; + +export '/src/controller/home/_controller.dart'; diff --git a/example/lib/src/controller/app/app_controller.dart b/example/lib/src/controller/app/app_controller.dart index 6f6f7db..9dbec29 100644 --- a/example/lib/src/controller/app/app_controller.dart +++ b/example/lib/src/controller/app/app_controller.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/view.dart'; +import '/src/controller.dart'; + +import '/src/view.dart'; /// The 'App Level' Controller -class ExampleAppController extends StateXController { +class ExampleAppController extends StateXController with EventsControllerMixin { /// Singleton design pattern is best for Controllers. factory ExampleAppController() => _this ??= ExampleAppController._(); ExampleAppController._(); @@ -37,67 +39,4 @@ class ExampleAppController extends StateXController { /// Allow for a Splash screen or not bool splashScreen = false; - - /// The framework will call this method exactly once. - /// Only when the [StateX] object is first created. - @override - void initState() { - super.initState(); - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: initState in $state'); - } - } - - /// The framework calls this method whenever this [StateX] object is closed. - @override - void deactivate() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: deactivate in $state'); - } - } - - /// Called when this object was [deactivate]. - /// Rarely actually called but good to have since was deactivated. - /// You may have to undo what was done in [deactivate]. - /// It was removed from the widget tree for some reason but re-inserted again. - @override - void activate() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: activate in $state'); - } - } - - /// The framework calls this method when this [StateX] object will be garbage collected - /// Note: YOU HAVE NO IDEA WHEN THIS WILL RUN in the Framework. - /// USE [deactivate] for time critical operations.[dispose] for light cleanup. - @override - void dispose() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: dispose in $state'); - } - super.dispose(); - } - - /// The application is not currently visible to the user, not responding to - /// user input, and running in the background. - @override - void pausedLifecycleState() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: pausedLifecycleState in $state'); - } - } - - /// The application is visible and responding to user input. - @override - void resumedLifecycleState() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: resumedLifecycleState in $state'); - } - } } diff --git a/example/lib/src/controller/app/common/events_controller.dart b/example/lib/src/controller/app/common/events_controller.dart new file mode 100644 index 0000000..8175d18 --- /dev/null +++ b/example/lib/src/controller/app/common/events_controller.dart @@ -0,0 +1,283 @@ +// Copyright 2024 Andrious Solutions Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show Locale; + +import 'package:flutter/foundation.dart'; + +import '/src/controller.dart'; + +/// +mixin EventsControllerMixin on StateXController { + /// + String get className { + if (_className == null) { + final name = '$this'; + final hash = name.indexOf('#'); + if (hash > 0) { + _className = name.substring(0, hash); + } else { + final parts = name.split(' '); + _className = parts.last; + } + } + return _className!; + } + + set className(String? name) => + _className ??= name?.isEmpty ?? true ? null : name; + + String? _className; + + @override + void initState() { + super.initState(); + assert(() { + if (kDebugMode) { + print('=========== initState() in $className\n'); + } + return true; + }()); + } + + /// Whenever it removes + @override + void deactivate() { + assert(() { + if (kDebugMode) { + print('=========== deactivate() in $className\n'); + } + return true; + }()); + super.deactivate(); + } + + /// Called when this object is reinserted into the tree after having been + /// removed via [deactivate]. + @override + void activate() { + super.activate(); + assert(() { + if (kDebugMode) { + print('=========== activate() in $className\n'); + } + return true; + }()); + } + + /// The framework calls this method when this StateX object will never + /// build again. + /// Note: YOU WILL HAVE NO IDEA WHEN THIS WILL RUN in the Framework. + @override + void dispose() { + assert(() { + if (kDebugMode) { + print('=========== dispose() in $className\n'); + } + return true; + }()); + super.dispose(); + } + + /// Called when this State is *first* added to as a Route observer?! + @override + void didPush() { + super.didPush(); + assert(() { + if (kDebugMode) { + print('=========== didPush() in $className\n'); + } + return true; + }()); + } + + /// New route has been pushed, and this State object's route is no longer current. + @override + void didPushNext() { + super.didPushNext(); + assert(() { + if (kDebugMode) { + print('=========== didPushNext() in $className\n'); + } + return true; + }()); + } + + /// Called when this State is popped off a route. + @override + void didPop() { + assert(() { + if (kDebugMode) { + print('=========== didPop() in $className\n'); + } + return true; + }()); + super.didPop(); + } + + /// The top route has been popped off, and this route shows up. + @override + void didPopNext() { + assert(() { + if (kDebugMode) { + print('=========== didPopNext() in $className\n'); + } + return true; + }()); + super.didPopNext(); + } + + /// This method is also called immediately after [initState]. + /// Otherwise called only if this State object's Widget + /// is a 'dependency' of InheritedWidget. + /// When a InheritedWidget's build() function is called + /// the dependent widget's build() function is also called but not before + /// their didChangeDependencies() function. + @override + void didChangeDependencies() { + super.didChangeDependencies(); + assert(() { + if (kDebugMode) { + print('=========== didChangeDependencies() in $className\n'); + } + return true; + }()); + } + + /// Called when the application's dimensions change. For example, + /// when a phone is rotated. + @override + void didChangeMetrics() { + super.didChangeMetrics(); + assert(() { + if (kDebugMode) { + print('=========== didChangeMetrics() in $className\n'); + } + return true; + }()); + } + + /// Called when the platform's text scale factor changes. + @override + void didChangeTextScaleFactor() { + super.didChangeTextScaleFactor(); + assert(() { + if (kDebugMode) { + print('=========== didChangeTextScaleFactor() in $className\n'); + } + return true; + }()); + } + + /// Brightness changed. + @override + void didChangePlatformBrightness() { + super.didChangePlatformBrightness(); + assert(() { + if (kDebugMode) { + print('=========== didChangePlatformBrightness() in $className\n'); + } + return true; + }()); + } + + /// Called when the system tells the app that the user's locale has changed. + @override + void didChangeLocales(List? locales) { + super.didChangeLocales(locales); + assert(() { + if (kDebugMode) { + print('=========== didChangeLocales() in $className\n'); + } + return true; + }()); + } + + /// Either be in the progress of attaching when the engine is first initializing + /// or after the view being destroyed due to a Navigator pop. + @override + void detachedAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('=========== detachedAppLifecycleState() in $className\n'); + } + return true; + }()); + super.detachedAppLifecycleState(); + } + + /// The application is visible and responding to user input. + @override + void resumedAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('=========== resumedAppLifecycleState() in $className\n'); + } + return true; + }()); + } + + /// The application is in an inactive state and is not receiving user input. + /// Apps in this state should assume that they may be [pausedAppLifecycleState] at any time. + @override + void inactiveAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('=========== inactiveAppLifecycleState() in $className\n'); + } + return true; + }()); + } + + /// All views of an application are hidden, either because the application is + /// about to be paused (on iOS and Android), or because it has been minimized + /// or placed on a desktop that is no longer visible (on non-web desktop), or + /// is running in a window or tab that is no longer visible (on the web). + @override + void hiddenAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('=========== hiddenAppLifecycleState() in $className\n'); + } + return true; + }()); + } + + /// The application is not currently visible to the user, not responding to + /// user input, and running in the background. + @override + void pausedAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('=========== pausedAppLifecycleState() in $className\n'); + } + return true; + }()); + super.pausedAppLifecycleState(); + } + + /// Called when there's a memory constraint. + @override + void didHaveMemoryPressure() { + super.didHaveMemoryPressure(); + assert(() { + if (kDebugMode) { + print('=========== didHaveMemoryPressure() in $className\n'); + } + return true; + }()); + } + + /// Called when the system changes the set of active accessibility features. + @override + void didChangeAccessibilityFeatures() { + super.didChangeAccessibilityFeatures(); + assert(() { + if (kDebugMode) { + print('=========== didChangeAccessibilityFeatures() in $className\n'); + } + return true; + }()); + } +} diff --git a/example/lib/src/controller/home/another_controller.dart b/example/lib/src/controller/home/another_controller.dart index 0e2c98a..aa7de55 100644 --- a/example/lib/src/controller/home/another_controller.dart +++ b/example/lib/src/controller/home/another_controller.dart @@ -4,14 +4,15 @@ import 'dart:ui' show AppExitResponse; -import 'package:example/src/controller.dart' - show ExampleAppController, StateXController; +import '/src/controller.dart' + show EventsControllerMixin, ExampleAppController, StateXController; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// Multiple Controllers can be assigned to one State object. /// Includes the mixin, StateXonErrorMixin, to supply an error handler -class AnotherController extends StateXController with StateXonErrorMixin { +class AnotherController extends StateXController + with EventsControllerMixin, StateXonErrorMixin { /// It's a good practice to make Controllers using the Singleton pattern factory AnotherController() => _this ??= AnotherController._(); AnotherController._() : super(); @@ -132,6 +133,7 @@ class AnotherController extends StateXController with StateXonErrorMixin { /// AppLifecycleState.resumed /// AppLifecycleState.inactive (may be paused at any time) /// AppLifecycleState.suspending (Android only) + super.didChangeAppLifecycleState(state); } /// Called when the system is running low on memory. diff --git a/example/lib/src/controller/home/controller.dart b/example/lib/src/controller/home/controller.dart index cec1229..b27ff8a 100644 --- a/example/lib/src/controller/home/controller.dart +++ b/example/lib/src/controller/home/controller.dart @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/controller.dart'; +import '/src/controller.dart'; -import 'package:example/src/model.dart'; +import '/src/model.dart'; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// -class Controller extends StateXController { +class Controller extends StateXController with EventsControllerMixin { /// It's a good practice and follow the Singleton pattern. /// There's on need for more than one instance of this particular class. factory Controller([StateX? state]) => _this ??= Controller._(state); @@ -58,282 +58,31 @@ class Controller extends StateXController { return !ExampleAppController().allowErrors; } - /// The framework will call this method exactly once. - /// Only when the [StateX] object is first created. - @override - void initState() { - super.initState(); - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: initState in $state'); - } - } - - /// The framework calls this method when the [StateX] object removed from widget tree. - /// i.e. The screen is closed. - @override - void deactivate() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: deactivate in Controller for $state'); - } - } - - /// Called when this State object was removed from widget tree for some reason - /// Undo what was done when [deactivate] was called. - @override - void activate() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: activate in Controller for $state'); - } - } - - /// The framework calls this method when this [StateX] object will never - /// build again. - /// Note: YOU DON'T KNOW WHEN THIS WILL RUN in the Framework. - /// PERFORM ANY TIME-CRITICAL OPERATION IN deactivate() INSTEAD! - @override - void dispose() { - if (inDebugMode) { - //ignore: avoid_print - print('############ now disposed.'); - } - super.dispose(); - } - - /// The application is not currently visible to the user, not responding to - /// user input, and running in the background. - @override - void pausedLifecycleState() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: pausedLifecycleState in Controller for $state'); - } - } - - /// Called when app returns from the background - @override - void resumedLifecycleState() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: resumedLifecycleState in Controller for $state'); - } - } - - /// If a State object is unexpectedly re-created - /// You have to 'update' the properties of the new StateX object using the - /// old StateX object because it's going to be disposed of. - @override - void updateNewStateX(oldState) { - /// When a State object destroyed and a new one is re-created! - /// This new StateX object may need to be updated with the old State object - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: updateNewStateX in Controller for $state'); - } - } - - /// The application is in an inactive state and is not receiving user input. - @override - void inactiveLifecycleState() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: inactiveLifecycleState in Controller for $state'); - } - } - - /// Either be in the progress of attaching when the engine is first initializing - /// or after the view being destroyed due to a Navigator pop. - @override - void detachedLifecycleState() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: detachedLifecycleState in Controller for $state'); - } - } - - /// Override this method to respond when the [StatefulWidget] is recreated. - @override - void didUpdateWidget(StatefulWidget oldWidget) { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didUpdateWidget in Controller for $state'); - } - } - - /// Called when this [StateX] object is first created immediately after [initState]. - /// Otherwise called only if this [State] object's Widget - /// is a dependency of [InheritedWidget]. - @override - void didChangeDependencies() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didChangeDependencies in Controller for $state'); - } - } - - /// Called whenever the application is reassembled during debugging, for - /// example during hot reload. - @override - void reassemble() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: reassemble in Controller for $state'); - } - } - - /// Called when the system tells the app to pop the current route. - /// For example, on Android, this is called when the user presses - /// the back button. - @override - Future didPopRoute() async { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didPopRoute in Controller for $state'); - } - return super.didPopRoute(); - } - - /// Called when the host tells the app to push a new route onto the - /// navigator. - @override - Future didPushRoute(String route) async { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didPushRoute in Controller for $state'); - } - return super.didPushRoute(route); - } - - /// Called when the host tells the application to push a new - /// [RouteInformation] and a restoration state onto the router. - @override - Future didPushRouteInformation(RouteInformation routeInformation) { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didPushRouteInformation in Controller for $state'); - } - return super.didPushRouteInformation(routeInformation); - } - /// The top route has been popped off, and this route shows up. @override void didPopNext() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didPopNext in Controller for $state'); - } + super.didPopNext(); setState(() {}); } /// Called when this route has been pushed. @override void didPush() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didPush in Controller for $state'); - } + super.didPush(); setState(() {}); } /// Called when this route has been popped off. @override void didPop() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didPop in Controller for $state'); - } + super.didPop(); setState(() {}); } /// New route has been pushed, and this route is no longer visible. @override void didPushNext() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didPushNext in Controller for $state'); - } + super.didPushNext(); setState(() {}); } - - /// Called when the application's dimensions change. For example, - /// when a phone is rotated. - @override - void didChangeMetrics() { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didChangeMetrics in Controller for $state'); - } - } - - /// Called when the platform's text scale factor changes. - @override - void didChangeTextScaleFactor() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didChangeTextScaleFactor in Controller for $state'); - } - } - - /// Brightness changed. - @override - void didChangePlatformBrightness() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didChangePlatformBrightness in Controller for $state'); - } - } - - /// Called when the system tells the app that the user's locale has changed. - @override - void didChangeLocales(List? locales) { - if (inDebugMode) { - //ignore: avoid_print - print('############ Event: didChangeLocale in Controller for $state'); - } - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - /// Passing these possible values: - /// AppLifecycleState.inactive (may be paused at any time) - /// AppLifecycleState.paused (may enter the suspending state at any time) - /// AppLifecycleState.detach - /// AppLifecycleState.resume - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didChangeAppLifecycleState in Controller for ${this.state}'); - } - } - - /// Called when the system is running low on memory. - @override - void didHaveMemoryPressure() { - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didHaveMemoryPressure in Controller for $state'); - } - } - - /// Called when the system changes the set of active accessibility features. - @override - void didChangeAccessibilityFeatures() { - // inDebugger is deprecated but still tested here. Use inDebugMode instead. - if (inDebugMode) { - //ignore: avoid_print - print( - '############ Event: didChangeAccessibilityFeatures in Controller for $state'); - } - } } diff --git a/example/lib/src/controller/home/counter_timer.dart b/example/lib/src/controller/home/counter_timer.dart index a88c7fa..d9c7b8f 100644 --- a/example/lib/src/controller/home/counter_timer.dart +++ b/example/lib/src/controller/home/counter_timer.dart @@ -1,12 +1,12 @@ // import 'dart:async'; -import 'package:example/src/controller.dart'; +import '/src/controller.dart'; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// -class CounterTimer extends StateXController { +class CounterTimer extends StateXController with EventsControllerMixin { /// Only one instance of the class is required. factory CounterTimer({ int? seconds, diff --git a/example/lib/src/controller/home/yet_another_controller.dart b/example/lib/src/controller/home/yet_another_controller.dart index aec67be..fa274ea 100644 --- a/example/lib/src/controller/home/yet_another_controller.dart +++ b/example/lib/src/controller/home/yet_another_controller.dart @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/view.dart'; +import '/src/controller.dart'; + +import '/src/view.dart'; /// Yet another Controller for demonstration purposes. /// Includes the mixin, StateXonErrorMixin, to supply an error handler -class YetAnotherController extends StateXController with StateXonErrorMixin { +class YetAnotherController extends StateXController + with EventsControllerMixin, StateXonErrorMixin { /// It's a good practice to make Controllers using the Singleton pattern factory YetAnotherController() => _this ??= YetAnotherController._(); YetAnotherController._() : super(); @@ -17,7 +20,7 @@ class YetAnotherController extends StateXController with StateXonErrorMixin { void onError(FlutterErrorDetails details) { if (inDebugMode) { //ignore: avoid_print - print('############ Event: onError in YetAnotherController for $state'); + print('============ Event: onError in YetAnotherController for $state'); } } } diff --git a/example/lib/src/view.dart b/example/lib/src/view.dart index 844c437..56f76f0 100644 --- a/example/lib/src/view.dart +++ b/example/lib/src/view.dart @@ -4,15 +4,17 @@ // Flutter's Material Interface package -export 'package:example/src/view/app/my_app.dart'; - -export 'package:example/src/view/app/splash_screen.dart'; - -export 'package:example/src/view/home/_view.dart'; - export 'package:flutter/cupertino.dart' show CupertinoSwitch; /// StateSetter is also defined in state_extended.dart export 'package:flutter/material.dart' hide StateSetter; export 'package:state_extended/state_extended.dart'; + +export '/src/view/app/common/events_state.dart'; + +export '/src/view/app/my_app.dart'; + +export '/src/view/app/splash_screen.dart'; + +export '/src/view/home/_view.dart'; diff --git a/example/lib/src/view/app/common/events_state.dart b/example/lib/src/view/app/common/events_state.dart new file mode 100644 index 0000000..48e24c8 --- /dev/null +++ b/example/lib/src/view/app/common/events_state.dart @@ -0,0 +1,281 @@ +// Copyright 2024 Andrious Solutions Ltd. 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/foundation.dart'; + +import '/src/view.dart'; + +/// +mixin EventsStateMixin on StateX { + /// + String get className { + if (_className == null) { + final name = '$this'; + final hash = name.indexOf('#'); + if (hash > 0) { + _className = name.substring(0, hash); + } else { + final parts = name.split(' '); + _className = parts.last; + } + } + return _className!; + } + + set className(String? name) => + _className ??= name?.isEmpty ?? true ? null : name; + + String? _className; + + @override + void initState() { + super.initState(); + assert(() { + if (kDebugMode) { + print('########### initState() in $className\n'); + } + return true; + }()); + } + + /// Whenever it removes + @override + void deactivate() { + assert(() { + if (kDebugMode) { + print('########### deactivate() in $className\n'); + } + return true; + }()); + super.deactivate(); + } + + /// Called when this object is reinserted into the tree after having been + /// removed via [deactivate]. + @override + void activate() { + super.activate(); + assert(() { + if (kDebugMode) { + print('########### activate() in $className\n'); + } + return true; + }()); + } + + /// The framework calls this method when this [StateX] object will never + /// build again. + /// Note: YOU WILL HAVE NO IDEA WHEN THIS WILL RUN in the Framework. + @override + void dispose() { + assert(() { + if (kDebugMode) { + print('########### dispose() in $className\n'); + } + return true; + }()); + super.dispose(); + } + + /// Called when this State is *first* added to as a Route observer?! + @override + void didPush() { + super.didPush(); + assert(() { + if (kDebugMode) { + print('########### didPush() in $className\n'); + } + return true; + }()); + } + + /// New route has been pushed, and this State object's route is no longer current. + @override + void didPushNext() { + super.didPushNext(); + assert(() { + if (kDebugMode) { + print('########### didPushNext() in $className\n'); + } + return true; + }()); + } + + /// Called when this State is popped off a route. + @override + void didPop() { + assert(() { + if (kDebugMode) { + print('########### didPop() in $className\n'); + } + return true; + }()); + super.didPop(); + } + + /// The top route has been popped off, and this route shows up. + @override + void didPopNext() { + assert(() { + if (kDebugMode) { + print('########### didPopNext() in $className\n'); + } + return true; + }()); + super.didPopNext(); + } + + /// This method is also called immediately after [initState]. + /// Otherwise called only if this [State] object's Widget + /// is a 'dependency' of [InheritedWidget]. + /// When a InheritedWidget's build() function is called + /// the dependent widget's build() function is also called but not before + /// their didChangeDependencies() function. + @override + void didChangeDependencies() { + super.didChangeDependencies(); + assert(() { + if (kDebugMode) { + print('########### didChangeDependencies() in $className\n'); + } + return true; + }()); + } + + /// Called when the application's dimensions change. For example, + /// when a phone is rotated. + @override + void didChangeMetrics() { + super.didChangeMetrics(); + assert(() { + if (kDebugMode) { + print('########### didChangeMetrics() in $className\n'); + } + return true; + }()); + } + + /// Called when the platform's text scale factor changes. + @override + void didChangeTextScaleFactor() { + super.didChangeTextScaleFactor(); + assert(() { + if (kDebugMode) { + print('########### didChangeTextScaleFactor() in $className\n'); + } + return true; + }()); + } + + /// Brightness changed. + @override + void didChangePlatformBrightness() { + super.didChangePlatformBrightness(); + assert(() { + if (kDebugMode) { + print('########### didChangePlatformBrightness() in $className\n'); + } + return true; + }()); + } + + /// Called when the system tells the app that the user's locale has changed. + @override + void didChangeLocales(List? locales) { + super.didChangeLocales(locales); + assert(() { + if (kDebugMode) { + print('########### didChangeLocales() in $className\n'); + } + return true; + }()); + } + + /// Either be in the progress of attaching when the engine is first initializing + /// or after the view being destroyed due to a Navigator pop. + @override + void detachedAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('########### detachedAppLifecycleState() in $className\n'); + } + return true; + }()); + super.detachedAppLifecycleState(); + } + + /// The application is visible and responding to user input. + @override + void resumedAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('########### resumedAppLifecycleState() in $className\n'); + } + return true; + }()); + } + + /// The application is in an inactive state and is not receiving user input. + /// Apps in this state should assume that they may be [pausedAppLifecycleState] at any time. + @override + void inactiveAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('########### inactiveAppLifecycleState() in $className\n'); + } + return true; + }()); + } + + /// All views of an application are hidden, either because the application is + /// about to be paused (on iOS and Android), or because it has been minimized + /// or placed on a desktop that is no longer visible (on non-web desktop), or + /// is running in a window or tab that is no longer visible (on the web). + @override + void hiddenAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('########### hiddenAppLifecycleState() in $className\n'); + } + return true; + }()); + } + + /// The application is not currently visible to the user, not responding to + /// user input, and running in the background. + @override + void pausedAppLifecycleState() { + assert(() { + if (kDebugMode) { + print('########### pausedAppLifecycleState() in $className\n'); + } + return true; + }()); + super.pausedAppLifecycleState(); + } + + /// Called when there's a memory constraint. + @override + void didHaveMemoryPressure() { + super.didHaveMemoryPressure(); + assert(() { + if (kDebugMode) { + print('########### didHaveMemoryPressure() in $className\n'); + } + return true; + }()); + } + + /// Called when the system changes the set of active accessibility features. + @override + void didChangeAccessibilityFeatures() { + super.didChangeAccessibilityFeatures(); + assert(() { + if (kDebugMode) { + print('########### didChangeAccessibilityFeatures() in $className\n'); + } + return true; + }()); + } +} diff --git a/example/lib/src/view/app/my_app.dart b/example/lib/src/view/app/my_app.dart index b81692b..12a2e23 100644 --- a/example/lib/src/view/app/my_app.dart +++ b/example/lib/src/view/app/my_app.dart @@ -18,7 +18,7 @@ class MyApp extends StatefulWidget { } /// -class _MyAppState extends AppStateX { +class _MyAppState extends AppStateX with EventsStateMixin { // _MyAppState() : super( diff --git a/example/lib/src/view/home/home_page.dart b/example/lib/src/view/home/home_page.dart index 6daf954..3b6167b 100644 --- a/example/lib/src/view/home/home_page.dart +++ b/example/lib/src/view/home/home_page.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/controller.dart'; +import '/src/controller.dart'; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// The Home page class HomePage extends StatefulWidget { @@ -18,7 +18,7 @@ class HomePage extends StatefulWidget { State createState() => _HomePageState(); } -class _HomePageState extends StateX { +class _HomePageState extends StateX with EventsStateMixin { /// Let the 'business logic' run in a Controller _HomePageState() : super(controller: Controller()) { /// Acquire a reference to the passed Controller. diff --git a/example/lib/src/view/home/page_01.dart b/example/lib/src/view/home/page_01.dart index 68b798f..bbf739f 100644 --- a/example/lib/src/view/home/page_01.dart +++ b/example/lib/src/view/home/page_01.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/controller.dart'; +import '/src/controller.dart'; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// The first page displayed in this app. class Page1 extends StatefulWidget { @@ -16,7 +16,7 @@ class Page1 extends StatefulWidget { } /// -class Page1State extends StateX { +class Page1State extends StateX with EventsStateMixin { /// Supply a controller to this State object /// so to call its setState() function below. Page1State() diff --git a/example/lib/src/view/home/page_02.dart b/example/lib/src/view/home/page_02.dart index 12d2c3f..e913886 100644 --- a/example/lib/src/view/home/page_02.dart +++ b/example/lib/src/view/home/page_02.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/controller.dart'; +import '/src/controller.dart'; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// The second page displayed in this app. class Page2 extends StatefulWidget { @@ -17,7 +17,7 @@ class Page2 extends StatefulWidget { /// This works with a separate 'data source' /// It a separate data source, and so the count is never reset to zero. -class Page2State extends StateX { +class Page2State extends StateX with EventsStateMixin { /// Define an InheritedWidget to be inserted above this Widget on the Widget tree. Page2State() : super(controller: Controller()) { /// Cast to type, Controller diff --git a/example/lib/src/view/home/page_03.dart b/example/lib/src/view/home/page_03.dart index dd9c69e..77b17ac 100644 --- a/example/lib/src/view/home/page_03.dart +++ b/example/lib/src/view/home/page_03.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:example/src/another_app/view.dart' as i; +import '/src/another_app/view.dart' as i; -import 'package:example/src/controller.dart'; +import '/src/controller.dart'; -import 'package:example/src/view.dart'; +import '/src/view.dart'; /// The third page displayed in this app. class Page3 extends StatefulWidget { @@ -17,7 +17,9 @@ class Page3 extends StatefulWidget { State createState() => _Page3State(); } -class _Page3State extends StateIn { +class _Page3State extends StateX with EventsStateMixin { + // Use built-in InheritedWidget + _Page3State() : super(useInherited: true); // int count = 0; diff --git a/example/test/test_event_handling.dart b/example/test/test_event_handling.dart index 102b62d..24085b1 100644 --- a/example/test/test_event_handling.dart +++ b/example/test/test_event_handling.dart @@ -10,10 +10,12 @@ const _location = '========================== test_event_handling.dart'; /// Simulate some App 'life cycle' events. Future testEventHandling(WidgetTester tester) async { - // + // Simulate a 'release focus' then refocus event tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); - tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.detached); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); // Give the app time to recover and indeed resume testing. diff --git a/lib/state_extended.dart b/lib/state_extended.dart index bf87462..8b4a9dc 100644 --- a/lib/state_extended.dart +++ b/lib/state_extended.dart @@ -196,11 +196,12 @@ abstract class StateX extends State bool _setStateRequested = false; /// This is the 'latest' State being viewed by the App. + @Deprecated('Use getter, isLastState, instead') bool get isEndState => this == lastState; + bool get isLastState => isEndState; /// Asynchronous operations must complete successfully. @override - @protected @mustCallSuper Future initAsync() async { // Always return true. It's got to continue for now. @@ -241,7 +242,6 @@ abstract class StateX extends State /// The framework will call this method exactly once. /// Only when the [StateX] object is first created. @override - @protected @mustCallSuper void initState() { assert(mounted, '${toString()} is not instantiated properly.'); @@ -288,7 +288,6 @@ abstract class StateX extends State /// the dependent widget's build() function is also called but not before /// their didChangeDependencies() function. Subclasses rarely use this method. @override - @protected @mustCallSuper void didChangeDependencies() { // Important to 'markNeedsBuild()' first @@ -310,7 +309,6 @@ abstract class StateX extends State /// Called when this object is reinserted into the tree after having been /// removed via [deactivate]. @override - @protected @mustCallSuper void activate() { /// In most cases, after a [State] object has been deactivated, it is _not_ @@ -354,7 +352,6 @@ abstract class StateX extends State /// The framework calls this method whenever it removes this [State] object /// from the tree. @override - @protected @mustCallSuper void deactivate() { /// The framework calls this method whenever it removes this [State] object @@ -395,7 +392,6 @@ abstract class StateX extends State /// The framework calls this method when this [StateX] object will never /// build again and will be disposed of with garbage collection. @override - @protected @mustCallSuper void dispose() { /// The State object's lifecycle is terminated. @@ -439,7 +435,6 @@ abstract class StateX extends State /// Returning to this app from another app will re-create the State object /// You 'update' the current State object using this function. @override - @protected @mustCallSuper void updateNewStateX(covariant StateX oldState) { /// No 'setState()' functions are allowed @@ -457,7 +452,6 @@ abstract class StateX extends State /// The framework always calls [build] after calling [didUpdateWidget], which /// means any calls to [setState] in [didUpdateWidget] are redundant. @override - @protected @mustCallSuper void didUpdateWidget(StatefulWidget oldWidget) { /// No 'setState()' functions are allowed @@ -476,9 +470,9 @@ abstract class StateX extends State _setStateRequested = false; } - /// Called when the system puts the app in the background or returns the app to the foreground. + /// Called when the system puts the app in the background + /// or returns the app to the foreground. @override - @protected @mustCallSuper void didChangeAppLifecycleState(AppLifecycleState state) { /// No 'setState()' functions are allowed to fully function at this point. @@ -487,46 +481,62 @@ abstract class StateX extends State /// First, process the State object's own event functions. switch (state) { case AppLifecycleState.inactive: - inactive = true; - inactiveLifecycleState(); + _inactiveAppLifecycle = true; + inactiveAppLifecycleState(); + _hiddenAppLifecycle = false; break; case AppLifecycleState.hidden: - hidden = true; - hiddenLifecycleState(); + _hiddenAppLifecycle = true; + hiddenAppLifecycleState(); + _pausedAppLifecycle = false; break; case AppLifecycleState.paused: - paused = true; - pausedLifecycleState(); + _pausedAppLifecycle = true; + pausedAppLifecycleState(); + _detachedAppLifecycle = false; + _resumedAppLifecycle = false; break; case AppLifecycleState.detached: - detached = true; - detachedLifecycleState(); + _detachedAppLifecycle = true; + detachedAppLifecycleState(); + // if (!deactivated) { + // // Not called otherwise? + // deactivate(); + // } + // if (!disposed) { + // // Not called otherwise? + // dispose(); + // } break; case AppLifecycleState.resumed: - resumed = true; // The StateX object now resumed will be re-created. - resumedLifecycleState(); + _resumedAppLifecycle = true; + resumedAppLifecycleState(); + _inactiveAppLifecycle = false; break; default: - // WARNING: Missing case clause for 'hidden'?? + // WARNING: Missing case clause } for (final con in controllerList) { con.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.inactive: - con.inactiveLifecycleState(); + con.inactiveAppLifecycleState(); + break; + case AppLifecycleState.hidden: + con.hiddenAppLifecycleState(); break; case AppLifecycleState.paused: - con.pausedLifecycleState(); + con.pausedAppLifecycleState(); break; case AppLifecycleState.detached: - con.detachedLifecycleState(); + con.detachedAppLifecycleState(); break; case AppLifecycleState.resumed: - con.resumedLifecycleState(); + con.resumedAppLifecycleState(); break; default: - // WARNING: Missing case clause for 'hidden'?? + // WARNING: Missing case clause } } @@ -544,48 +554,66 @@ abstract class StateX extends State /// Apps in this state should assume that they may be [pausedLifecycleState] at any time. @override - @protected + @Deprecated('Use inactiveAppLifecycleState instead') void inactiveLifecycleState() {} + @override + void inactiveAppLifecycleState() => inactiveLifecycleState(); /// State object was in 'inactive' state - bool inactive = false; + bool get inactiveAppLifecycle => _inactiveAppLifecycle; + bool _inactiveAppLifecycle = false; + /// All views of an application are hidden, either because the application is + /// about to be paused (on iOS and Android), or because it has been minimized + /// or placed on a desktop that is no longer visible (on non-web desktop), or + /// is running in a window or tab that is no longer visible (on the web). @override - @protected + @Deprecated('Use hiddenLifecycleState instead') void hiddenLifecycleState() {} + @override + void hiddenAppLifecycleState() => hiddenLifecycleState(); /// State object was in a 'hidden' state - bool hidden = false; + bool get hiddenAppLifecycle => _hiddenAppLifecycle; + bool _hiddenAppLifecycle = false; /// The application is not currently visible to the user, not responding to /// user input, and running in the background. @override - @protected + @Deprecated('Use pausedLifecycleState instead') void pausedLifecycleState() {} + @override + void pausedAppLifecycleState() {} /// State object was in 'paused' state - bool paused = false; + bool get pausedAppLifecycle => _pausedAppLifecycle; + bool _pausedAppLifecycle = false; /// Either be in the progress of attaching when the engine is first initializing /// or after the view being destroyed due to a Navigator pop. @override - @protected + @Deprecated('Use detachedLifecycleState instead') void detachedLifecycleState() {} + @override + void detachedAppLifecycleState() {} /// State object was in 'paused' state - bool detached = false; + bool get detachedAppLifecycle => _detachedAppLifecycle; + bool _detachedAppLifecycle = false; /// The application is visible and responding to user input. @override - @protected + @Deprecated('Use resumedLifecycleState instead') void resumedLifecycleState() {} + @override + void resumedAppLifecycleState() => resumedLifecycleState(); /// State object was in 'resumed' state - bool resumed = false; + bool get resumedAppLifecycle => _resumedAppLifecycle; + bool _resumedAppLifecycle = false; /// Called when a request is received from the system to exit the application. @override - @protected @mustCallSuper Future didRequestAppExit() async { /// Exiting the application can proceed. @@ -620,7 +648,7 @@ abstract class StateX extends State if (_setStateRequested && appResponse == AppExitResponse.cancel) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -636,7 +664,6 @@ abstract class StateX extends State /// true. If none return true, the application quits. /// @override - @protected @mustCallSuper Future didPopRoute() async { /// Observers are expected to return true if they were able to @@ -671,7 +698,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -685,7 +712,6 @@ abstract class StateX extends State /// navigator. /// @override - @protected @mustCallSuper Future didPushRoute(String route) async { /// Observers are expected to return true if they were able to @@ -717,7 +743,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -740,7 +766,6 @@ abstract class StateX extends State /// The default implementation is to call the [didPushRoute] directly with the /// [RouteInformation.uri]. @override - @protected @mustCallSuper Future didPushRouteInformation( RouteInformation routeInformation) async { @@ -767,7 +792,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -778,7 +803,6 @@ abstract class StateX extends State /// The top route has been popped off, and this route shows up. @override - @protected @mustCallSuper void didPopNext() { // Don't if the State object is defunct. @@ -798,7 +822,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { // Perform a 'rebuild' if requested. setState(() {}); } @@ -807,7 +831,6 @@ abstract class StateX extends State /// Called when this route has been pushed. @override - @protected @mustCallSuper void didPush() { // Don't if the State object is defunct. @@ -827,7 +850,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { // Perform a 'rebuild' if requested. setState(() {}); } @@ -836,7 +859,6 @@ abstract class StateX extends State /// Called when this route has been popped off. @override - @protected @mustCallSuper void didPop() { // Don't if the State object is defunct. @@ -856,7 +878,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { // Perform a 'rebuild' if requested. setState(() {}); } @@ -865,7 +887,6 @@ abstract class StateX extends State /// New route has been pushed, and this route is no longer visible. @override - @protected @mustCallSuper void didPushNext() { // Don't if the State object is defunct. @@ -885,7 +906,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { // Perform a 'rebuild' if requested. setState(() {}); } @@ -899,7 +920,6 @@ abstract class StateX extends State /// Called when the application's dimensions change. For example, /// when a phone is rotated. - @protected @override @mustCallSuper void didChangeMetrics() { @@ -934,7 +954,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -942,7 +962,6 @@ abstract class StateX extends State } /// Called when the platform's text scale factor changes. - @protected @override @mustCallSuper void didChangeTextScaleFactor() { @@ -978,7 +997,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -986,7 +1005,6 @@ abstract class StateX extends State } /// Called when the platform brightness changes. - @protected @override @mustCallSuper void didChangePlatformBrightness() { @@ -1010,7 +1028,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -1020,7 +1038,6 @@ abstract class StateX extends State /// Called when the system tells the app that the user's locale has /// changed. For example, if the user changes the system language /// settings. - @protected @mustCallSuper @override void didChangeLocales(List? locales) { @@ -1047,7 +1064,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -1055,7 +1072,6 @@ abstract class StateX extends State } /// Called when the system is running low on memory. - @protected @override @mustCallSuper void didHaveMemoryPressure() { @@ -1083,7 +1099,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -1091,7 +1107,6 @@ abstract class StateX extends State } /// Called when the system changes the set of currently active accessibility features. - @protected @override @mustCallSuper void didChangeAccessibilityFeatures() { @@ -1118,7 +1133,7 @@ abstract class StateX extends State if (_setStateRequested) { _setStateRequested = false; // Only the latest State is rebuilt - if (isEndState) { + if (isLastState) { /// Perform a 'rebuild' if requested. setState(() {}); } @@ -1128,7 +1143,6 @@ abstract class StateX extends State /// During development, if a hot reload occurs, the reassemble method is called. /// This provides an opportunity to reinitialize any data that was prepared /// in the initState method. - @protected @override @mustCallSuper void reassemble() { @@ -1614,10 +1628,10 @@ mixin SetStateMixin { // Testing Flutter lifecycle operation assert(() { - if (_oldStateX!.resumed || _oldStateX!.deactivated) { + if (_oldStateX!.resumedAppLifecycle || _oldStateX!.deactivated) { if (kDebugMode) { print( - '############ _pushStateToSetter(): resumed: ${_oldStateX!.resumed} deactivated: ${_oldStateX!.deactivated}'); + '############ _pushStateToSetter(): resumed: ${_oldStateX!.resumedAppLifecycle} deactivated: ${_oldStateX!.deactivated}'); } } return true; @@ -1777,7 +1791,7 @@ mixin StateListener implements RouteAware { /// Use the old one to update properties in the new StateX object. } - /// Override this method to respond when the [StatefulWidget] is recreated. + /// Override this method to respond to when the [StatefulWidget] is recreated. void didUpdateWidget(StatefulWidget oldWidget) { /// The framework always calls build() after calling [didUpdateWidget], which /// means any calls to [setState] in [didUpdateWidget] are redundant. @@ -1879,8 +1893,8 @@ mixin StateListener implements RouteAware { @override void didPopNext() {} - /// Called when the application's dimensions change. For example, - /// when a phone is rotated. + /// Called when the application's UI dimensions change. + /// For example, when a phone is rotated. void didChangeMetrics() { /// Called when the application's dimensions change. For example, /// when a phone is rotated. @@ -1937,10 +1951,14 @@ mixin StateListener implements RouteAware { /// Either be in the progress of attaching when the engine is first initializing /// or after the view being destroyed due to a Navigator pop. + @Deprecated('Use detachedAppLifecycleState instead') void detachedLifecycleState() {} + void detachedAppLifecycleState() => detachedLifecycleState(); /// The application is visible and responding to user input. + @Deprecated('Use resumedAppLifecycleState instead') void resumedLifecycleState() {} + void resumedAppLifecycleState() => resumedLifecycleState(); /// The application is in an inactive state and is not receiving user input. /// @@ -1956,17 +1974,23 @@ mixin StateListener implements RouteAware { /// a picture-in-picture app, a system dialog, or another window. /// /// Apps in this state should assume that they may be [pausedLifecycleState] at any time. + @Deprecated('Use inactiveAppLifecycleState instead') void inactiveLifecycleState() {} + void inactiveAppLifecycleState() => inactiveLifecycleState(); /// All views of an application are hidden, either because the application is /// about to be paused (on iOS and Android), or because it has been minimized /// or placed on a desktop that is no longer visible (on non-web desktop), or /// is running in a window or tab that is no longer visible (on the web). + @Deprecated('Use hiddenAppLifecycleState instead') void hiddenLifecycleState() {} + void hiddenAppLifecycleState() => hiddenLifecycleState(); /// The application is not currently visible to the user, not responding to /// user input, and running in the background. + @Deprecated('Use pausedAppLifecycleState instead') void pausedLifecycleState() {} + void pausedAppLifecycleState() => pausedLifecycleState(); /// Called when the system is running low on memory. void didHaveMemoryPressure() { @@ -2422,7 +2446,6 @@ class StateXInheritedWidget extends InheritedWidget { class _SetStateXWidget extends StatelessWidget { /// const _SetStateXWidget({ - super.key, required this.stateX, required this.widgetFunc, }); @@ -2442,7 +2465,7 @@ class _SetStateXWidget extends StatelessWidget { /// {@category Get started} /// {@category StateX class} /// {@category AppStateX class} -abstract class AppStateX extends StateIn +abstract class AppStateX extends StateX with _ControllersById { /// /// Optionally supply as many State Controllers as you like to work with this App. @@ -2452,7 +2475,8 @@ abstract class AppStateX extends StateIn List? controllers, Object? object, // Save the current error handler - }) : _currentErrorFunc = FlutterError.onError { + }) : _currentErrorFunc = FlutterError.onError, + super(useInherited: true) { //Record this as the 'root' State object. setRootStateX(this); _dataObj = object; @@ -2498,6 +2522,37 @@ abstract class AppStateX extends StateIn @protected Widget buildIn(BuildContext context); + /// Calls the deactivate() and dispose() functions + /// in all the app's StateX class objects + /// It's success will depending on the hosting operating system: + /// https://github.com/flutter/flutter/issues/124945#issuecomment-1514159238 + @override + @protected + @mustCallSuper + void detachedAppLifecycleState() { + // + forEachState((state) { + // + try { + state.deactivate(); + } catch (e, stack) { + // An error in the error handler. Record the error + recordException(e, stack); + _onErrorInHandler(); + } + + try { + if (!state.disposed) { + state.dispose(); + } + } catch (e, stack) { + // An error in the error handler. Record the error + recordException(e, stack); + _onErrorInHandler(); + } + }, reversed: true); + } + /// Clean up memory /// Called when garbage collecting @override @@ -2577,7 +2632,8 @@ abstract class AppStateX extends StateIn /// This 'widget function' will be called again. @override Widget state(WidgetBuilder? widgetFunc) { - widgetFunc ??= (_) => const SizedBox(); // Display 'nothing' if not provided + widgetFunc ??= + (_) => const SizedBox.shrink(); // Display 'nothing' if not provided return _SetStateXWidget(stateX: this, widgetFunc: widgetFunc); } @@ -3171,7 +3227,8 @@ mixin RecordExceptionMixin { /// {@category StateX class} /// {@category State Object Controller} mixin AsyncOps { - /// Used to complete asynchronous operations + /// Initialize any 'time-consuming' operations at the beginning. + /// Implement any asynchronous operations needed done at start up. Future initAsync() async => true; /// Supply an 'error handler' routine if something goes wrong diff --git a/pubspec.yaml b/pubspec.yaml index 0b26dd1..6456260 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: state_extended description: This class extends the capabilities of Flutter's State class and includes a controller. -version: 4.18.0 +version: 4.19.0 homepage: https://www.andrioussolutions.com repository: https://github.com/AndriousSolutions/state_extended