Skip to content

Commit

Permalink
Issues/39832 reland (#13642)
Browse files Browse the repository at this point in the history
* Reland "Added new lifecycle enum (#11913)"
  • Loading branch information
chunhtai committed Nov 5, 2019
1 parent 76312ee commit 1bfb928
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 29 deletions.
16 changes: 7 additions & 9 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,16 @@ enum AppLifecycleState {
///
/// When the application is in this state, the engine will not call the
/// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks.
///
/// Android apps in this state should assume that they may enter the
/// [suspending] state at any time.
paused,

/// The application will be suspended momentarily.
///
/// When the application is in this state, the engine will not call the
/// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks.
/// The application is still hosted on a flutter engine but is detached from
/// any host views.
///
/// On iOS, this state is currently unused.
suspending,
/// When the application is in this state, the engine is running without
/// a view. It can either be in the progress of attaching a view when engine
/// was first initializes, or after the view being destroyed due to a Navigator
/// pop.
detached,
}

/// A representation of distances for each of the four edges of a rectangle,
Expand Down
13 changes: 4 additions & 9 deletions lib/web_ui/lib/src/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,13 @@ enum AppLifecycleState {
///
/// When the application is in this state, the engine will not call the
/// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks.
///
/// Android apps in this state should assume that they may enter the
/// [suspending] state at any time.
paused,

/// The application will be suspended momentarily.
///
/// When the application is in this state, the engine will not call the
/// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks.
/// The application is detached from view.
///
/// On iOS, this state is currently unused.
suspending,
/// When the application is in this state, the engine is running without
/// a platform UI.
detached,
}

/// A representation of distances for each of the four edges of a rectangle,
Expand Down
2 changes: 1 addition & 1 deletion runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class RuntimeController final : public WindowClient {
std::string variant_code;
std::vector<std::string> locale_data;
std::string user_settings_data = "{}";
std::string lifecycle_state;
std::string lifecycle_state = "AppLifecycleState.detached";
bool semantics_enabled = false;
bool assistive_technology_enabled = false;
int32_t accessibility_feature_flags_ = 0;
Expand Down
2 changes: 1 addition & 1 deletion shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ bool Engine::HandleLifecyclePlatformMessage(PlatformMessage* message) {
const auto& data = message->data();
std::string state(reinterpret_cast<const char*>(data.data()), data.size());
if (state == "AppLifecycleState.paused" ||
state == "AppLifecycleState.suspending") {
state == "AppLifecycleState.detached") {
activity_running_ = false;
StopAnimator();
} else if (state == "AppLifecycleState.resumed" ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ void onDetach() {
platformPlugin = null;
}

flutterEngine.getLifecycleChannel().appIsDetached();

// Destroy our FlutterEngine if we're not set to retain it.
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ public void appIsPaused() {
channel.send("AppLifecycleState.paused");
}

public void appIsDetached() {
Log.v(TAG, "Sending AppLifecycleState.detached message.");
channel.send("AppLifecycleState.detached");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,21 @@ public void itSendsLifecycleEventsToFlutter() {
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();

// When the Activity/Fragment is resumed, a resumed message should have been sent to Flutter.
delegate.onResume();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();

// When the Activity/Fragment is paused, an inactive message should have been sent to Flutter.
delegate.onPause();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();

// When the Activity/Fragment is stopped, a paused message should have been sent to Flutter.
// Notice that Flutter uses the term "paused" in a different way, and at a different time
Expand All @@ -105,6 +108,14 @@ public void itSendsLifecycleEventsToFlutter() {
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();

// When activity detaches, a detached message should have been sent to Flutter.
delegate.onDetach();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached();
}

@Test
Expand Down
24 changes: 15 additions & 9 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,23 @@ - (void)ensureSemanticsEnabled {

- (void)setViewController:(FlutterViewController*)viewController {
FML_DCHECK(self.iosPlatformView);
_viewController = [viewController getWeakPtr];
_viewController =
viewController ? [viewController getWeakPtr] : fml::WeakPtr<FlutterViewController>();
self.iosPlatformView->SetOwnerViewController(_viewController);
[self maybeSetupPlatformViewChannels];

__block FlutterEngine* blockSelf = self;
self.flutterViewControllerWillDeallocObserver =
[[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
object:viewController
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* note) {
[blockSelf notifyViewControllerDeallocated];
}];
if (viewController) {
__block FlutterEngine* blockSelf = self;
self.flutterViewControllerWillDeallocObserver =
[[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
object:viewController
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* note) {
[blockSelf notifyViewControllerDeallocated];
}];
} else {
self.flutterViewControllerWillDeallocObserver = nil;
}
}

- (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer {
Expand All @@ -201,6 +206,7 @@ - (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer {
}

- (void)notifyViewControllerDeallocated {
[[self lifecycleChannel] sendMessage:@"AppLifecycleState.detached"];
if (!_allowHeadlessExecution) {
[self destroyContext];
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,70 @@ - (void)testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
[engine setViewController:nil];
}

- (void)testFlutterViewControllerDetachingSendsApplicationLifecycle {
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];

// Let the engine finish booting (at the end of which the channels are properly set-up) before
// moving onto the next step of showing the next view controller.
ScreenBeforeFlutter* rootVC = [[ScreenBeforeFlutter alloc] initWithEngineRunCompletion:^void() {
[engineStartedExpectation fulfill];
}];

[self waitForExpectationsWithTimeout:5 handler:nil];

UIApplication* application = UIApplication.sharedApplication;
application.delegate.window.rootViewController = rootVC;
FlutterEngine* engine = rootVC.engine;

NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];

// Expected sequence from showing the FlutterViewController is inactive and resumed.
[lifecycleExpectations addObjectsFromArray:@[
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
forStep:@"showing a FlutterViewController"],
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
forStep:@"showing a FlutterViewController"]
]];
// At the end of Flutter VC, we want to make sure it deallocs and sends detached signal.
// Using autoreleasepool will guarantee that.
FlutterViewController* flutterVC;
@autoreleasepool {
flutterVC = [rootVC showFlutter];
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@",
[nextExpectation expectedLifecycle], message);
return;
}

[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
}];

[self waitForExpectations:lifecycleExpectations timeout:5];

// Starts dealloc flutter VC.
[lifecycleExpectations addObjectsFromArray:@[
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
forStep:@"detaching a FlutterViewController"],
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.paused"
forStep:@"detaching a FlutterViewController"],
[[XCAppLifecycleTestExpectation alloc]
initForLifecycle:@"AppLifecycleState.detached"
forStep:@"detaching a FlutterViewController"]
]];
[flutterVC dismissViewControllerAnimated:NO completion:nil];
flutterVC = nil;
}
[self waitForExpectations:lifecycleExpectations timeout:5];

[engine.lifecycleChannel setMessageHandler:nil];
[engine setViewController:nil];
}

@end

0 comments on commit 1bfb928

Please sign in to comment.