Commit 9452a344 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Support inactive, suspending AppLifecycleStates (#10306)

* Rolls the engine to 75c74dc463d56e17be10315cfde409010fd8f90b.
* Adds framework support for the `AppLifecycleState.inactive` and `AppLifecycleState.suspending` states.

What are the new states?
------------------------
* `AppLifecycleState.inactive` is emitted on iOS only and corresponds to iOS's foreground inactive state. Current iOS state transitions are:
`resumed` <--> `inactive` <--> `paused`

* `AppLifecycleState.suspending` is currently emitted on Android only and corresponds to the
transition to Android's stopped state. Current Android state transitions are:
`resumed` <--> `paused` --> `suspending` --> `resumed`

These transitions may change in future.

This is a breaking change on iOS
--------------------------------

This changes the semantics of the `AppLifecycleState.paused` state on
iOS. The behaviour associated with this state is unchanged on Android.

For background on iOS application states see the state transition
diagram in the [App Programming Guide for iOS](https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html#//apple_ref/doc/uid/TP40007072-CH2-SW6).

On iOS, prior to this change, `AppLifecycleState.paused` corresponded to
a transition to the _foreground inactive_ state. It now corresponds to a
transition to the _background state_. The newly-added
`AppLifecycleState.inactive` state now corresponds to entering the
_foreground inactive_ state, which (currently) has no exact analogue on
Android.

Briefly, the _foreground inactive_ state is the state entered when
switching from an app to the app switcher, receiving a phone call, or
responding to a TouchID request. Apps are permitted to continue
animating/updating views in this state if desired. From the _foreground
inactive_ state, the app may transition back to _active_ (e.g., entering
the app switcher then resuming the app), or to the _background_ state
(e.g., switching to the home screen or another app).

What to change
--------------
If your app does not handle the `AppLifecycleState.paused` state in a
`WidgetsBindingObserver.didChangeAppLifecycleState` implementation, no
changes are required.

If you do handle `AppLifecycleState.paused`, you may additionally wish
to also handle `AppLifecycleState.inactive`. For example, games should
probably pause on entering the app switcher rather than wait to be
backgrounded.

More details
------------
For background on Android application states see the state transition
diagram in the [Android Activity Lifecycle docs](https://developer.android.com/guide/components/activities/activity-lifecycle.html#alc).
parent 7fb798f5
af60e003d06a82310d9d5240fdb6753729edb10f
75c74dc463d56e17be10315cfde409010fd8f90b
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'message_codecs.dart';
import 'platform_channel.dart';
......@@ -38,6 +40,9 @@ class SystemChannels {
);
/// A string [BasicMessageChannel] for lifecycle events.
///
/// Valid messages are string representations of the values of the
/// [AppLifecycleState] enumeration.
static const BasicMessageChannel<String> lifecycle = const BasicMessageChannel<String>(
'flutter/lifecycle',
const StringCodec(),
......
......@@ -221,6 +221,12 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
case 'AppLifecycleState.resumed':
handleAppLifecycleStateChanged(AppLifecycleState.resumed);
break;
case 'AppLifecycleState.inactive':
handleAppLifecycleStateChanged(AppLifecycleState.inactive);
break;
case 'AppLifecycleState.suspending':
handleAppLifecycleStateChanged(AppLifecycleState.suspending);
break;
}
return null;
}
......
......@@ -17,6 +17,15 @@ class MemoryPressureObserver extends WidgetsBindingObserver {
}
}
class AppLifecycleStateObserver extends WidgetsBindingObserver {
AppLifecycleState lifecycleState;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
lifecycleState = state;
}
}
void main() {
setUp(() {
WidgetsFlutterBinding.ensureInitialized();
......@@ -31,4 +40,25 @@ void main() {
expect(observer.sawMemoryPressure, true);
WidgetsBinding.instance.removeObserver(observer);
});
testWidgets('handleLifecycleStateChanged callback', (WidgetTester tester) async {
final AppLifecycleStateObserver observer = new AppLifecycleStateObserver();
WidgetsBinding.instance.addObserver(observer);
ByteData message = const StringCodec().encodeMessage('AppLifecycleState.paused');
await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
expect(observer.lifecycleState, AppLifecycleState.paused);
message = const StringCodec().encodeMessage('AppLifecycleState.resumed');
await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
expect(observer.lifecycleState, AppLifecycleState.resumed);
message = const StringCodec().encodeMessage('AppLifecycleState.inactive');
await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
expect(observer.lifecycleState, AppLifecycleState.inactive);
message = const StringCodec().encodeMessage('AppLifecycleState.suspending');
await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
expect(observer.lifecycleState, AppLifecycleState.suspending);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment