mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Connect the FlutterEngine to the FlutterSceneDelegate (#174910)
Once the `FlutterViewController` is able to get its `UIWindowScene`, pass itself to the `FlutterSceneDelegate` through the `FlutterSceneLifeCycleProvider` protocol. The scene will store the `FlutterEngine` in a weak pointer array. This is an incremental change towards https://github.com/flutter/flutter/issues/174398. Fixes https://github.com/flutter/flutter/issues/174395. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
ef6c1d1851
commit
ba2bcd62dc
@ -111,6 +111,8 @@ source_set("flutter_framework_source") {
|
||||
"framework/Source/FlutterRestorationPlugin.h",
|
||||
"framework/Source/FlutterRestorationPlugin.mm",
|
||||
"framework/Source/FlutterSceneDelegate.m",
|
||||
"framework/Source/FlutterSceneLifecycle.h",
|
||||
"framework/Source/FlutterSceneLifecycle.mm",
|
||||
"framework/Source/FlutterSemanticsScrollView.h",
|
||||
"framework/Source/FlutterSemanticsScrollView.mm",
|
||||
"framework/Source/FlutterSharedApplication.h",
|
||||
@ -262,6 +264,7 @@ if (enable_ios_unittests) {
|
||||
"framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm",
|
||||
"framework/Source/FlutterRestorationPluginTest.mm",
|
||||
"framework/Source/FlutterSceneDelegateTest.m",
|
||||
"framework/Source/FlutterSceneLifecycleTest.mm",
|
||||
"framework/Source/FlutterSharedApplicationTest.mm",
|
||||
"framework/Source/FlutterSpellCheckPluginTest.mm",
|
||||
"framework/Source/FlutterTextInputPluginTest.mm",
|
||||
|
||||
@ -6,12 +6,25 @@
|
||||
|
||||
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
|
||||
|
||||
FLUTTER_ASSERT_ARC
|
||||
|
||||
@interface FlutterSceneDelegate () <FlutterSceneLifeCycleProvider>
|
||||
@end
|
||||
|
||||
@implementation FlutterSceneDelegate
|
||||
|
||||
@synthesize sceneLifeCycleDelegate = _sceneLifeCycleDelegate;
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_sceneLifeCycleDelegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)scene:(UIScene*)scene
|
||||
willConnectToSession:(UISceneSession*)session
|
||||
options:(UISceneConnectionOptions*)connectionOptions {
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSCENELIFECYCLE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSCENELIFECYCLE_H_
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
|
||||
|
||||
/**
|
||||
* Propagates `UIWindowSceneDelegate` callbacks to the `FlutterEngine` to then be propograted to
|
||||
* registered plugins.
|
||||
*/
|
||||
@interface FlutterPluginSceneLifeCycleDelegate : NSObject
|
||||
|
||||
- (void)addFlutterEngine:(FlutterEngine*)engine;
|
||||
|
||||
- (void)removeFlutterEngine:(FlutterEngine*)engine;
|
||||
@end
|
||||
|
||||
/**
|
||||
* Implement this in the `UIWindowSceneDelegate` of your app to enable Flutter plugins to register
|
||||
* themselves to the scene life cycle events.
|
||||
*/
|
||||
@protocol FlutterSceneLifeCycleProvider
|
||||
@property(nonatomic, strong) FlutterPluginSceneLifeCycleDelegate* sceneLifeCycleDelegate;
|
||||
@end
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSCENELIFECYCLE_H_
|
||||
@ -0,0 +1,89 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle.h"
|
||||
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
|
||||
|
||||
FLUTTER_ASSERT_ARC
|
||||
|
||||
@interface FlutterPluginSceneLifeCycleDelegate ()
|
||||
|
||||
/**
|
||||
* An array of weak pointers to `FlutterEngine`s that have views within this scene.
|
||||
*
|
||||
* This array is lazily cleaned up. `updateEnginesInScene:` should be called before use to ensure it
|
||||
* is up-to-date.
|
||||
*/
|
||||
@property(nonatomic, strong) NSPointerArray* engines;
|
||||
@end
|
||||
|
||||
@implementation FlutterPluginSceneLifeCycleDelegate
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_engines = [NSPointerArray weakObjectsPointerArray];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addFlutterEngine:(FlutterEngine*)engine {
|
||||
// Check if the engine is already in the array to avoid duplicates.
|
||||
if ([self.engines.allObjects containsObject:engine]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.engines addPointer:(__bridge void*)engine];
|
||||
|
||||
// NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that
|
||||
// has set one of its values to nil, nothing could have changed and it can skip compaction.
|
||||
// That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray.
|
||||
// As a workaround, we mutate it first. See: http://www.openradar.me/15396578
|
||||
[self.engines addPointer:nil];
|
||||
[self.engines compact];
|
||||
}
|
||||
|
||||
- (void)removeFlutterEngine:(FlutterEngine*)engine {
|
||||
NSUInteger index = [self.engines.allObjects indexOfObject:engine];
|
||||
if (index != NSNotFound) {
|
||||
[self.engines removePointerAtIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateEnginesInScene:(UIScene*)scene {
|
||||
// Removes engines that are no longer in the scene or have been deallocated.
|
||||
//
|
||||
// This also handles the case where a FlutterEngine's view has been moved to a different scene.
|
||||
for (NSUInteger i = 0; i < self.engines.count; i++) {
|
||||
FlutterEngine* engine = (FlutterEngine*)[self.engines pointerAtIndex:i];
|
||||
|
||||
// The engine may be nil if it has been deallocated.
|
||||
if (engine == nil) {
|
||||
[self.engines removePointerAtIndex:i];
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// There aren't any events that inform us when a UIWindow changes scenes.
|
||||
// If a developer moves an entire UIWindow to a different scene and that window has a
|
||||
// FlutterView inside of it, its engine will still be in its original scene's
|
||||
// FlutterPluginSceneLifeCycleDelegate. The best we can do is move the engine to the correct
|
||||
// scene here. Due to this, when moving a UIWindow from one scene to another, its first scene
|
||||
// event may be lost. Since Flutter does not fully support multi-scene and this is an edge
|
||||
// case, this is a loss we can deal with. To workaround this, the developer can move the
|
||||
// UIView instead of the UIWindow, which will use willMoveToWindow to add/remove the engine from
|
||||
// the scene.
|
||||
UIWindowScene* engineScene = engine.viewController.view.window.windowScene;
|
||||
if (engineScene != nil && engineScene != scene) {
|
||||
[self.engines removePointerAtIndex:i];
|
||||
i--;
|
||||
|
||||
if ([engineScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
|
||||
id<FlutterSceneLifeCycleProvider> lifeCycleProvider =
|
||||
(id<FlutterSceneLifeCycleProvider>)engineScene.delegate;
|
||||
[lifeCycleProvider.sceneLifeCycleDelegate addFlutterEngine:engine];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
@ -0,0 +1,137 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle_Test.h"
|
||||
|
||||
FLUTTER_ASSERT_ARC
|
||||
|
||||
@interface FlutterSceneLifecycleTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation FlutterSceneLifecycleTest
|
||||
- (void)setUp {
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
}
|
||||
|
||||
- (void)testAddFlutterEngine {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
}
|
||||
|
||||
- (void)testAddDuplicateFlutterEngine {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
}
|
||||
|
||||
- (void)testAddMultipleFlutterEngine {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
|
||||
id mockEngine2 = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine2];
|
||||
|
||||
XCTAssertEqual(delegate.engines.count, 2.0);
|
||||
}
|
||||
|
||||
- (void)testRemoveFlutterEngine {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
|
||||
[delegate removeFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 0.0);
|
||||
}
|
||||
|
||||
- (void)testRemoveNotFoundFlutterEngine {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
XCTAssertEqual(delegate.engines.count, 0.0);
|
||||
|
||||
[delegate removeFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 0.0);
|
||||
}
|
||||
|
||||
- (void)testUpdateEnginesInSceneRemovesDeallocatedEngine {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
@autoreleasepool {
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
}
|
||||
|
||||
id mockWindowScene = OCMClassMock([UIWindowScene class]);
|
||||
|
||||
[delegate updateEnginesInScene:mockWindowScene];
|
||||
XCTAssertEqual(delegate.engines.count, 0.0);
|
||||
}
|
||||
|
||||
- (void)testUpdateEnginesInSceneRemovesEngineNotInScene {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
id mockViewController = OCMClassMock([UIViewController class]);
|
||||
id mockView = OCMClassMock([UIView class]);
|
||||
id mockWindow = OCMClassMock([UIWindow class]);
|
||||
id mockWindowScene = OCMClassMock([UIWindowScene class]);
|
||||
id mockLifecycleProvider = OCMProtocolMock(@protocol(FlutterSceneLifeCycleProvider));
|
||||
id mockLifecycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]);
|
||||
OCMStub([mockEngine viewController]).andReturn(mockViewController);
|
||||
OCMStub([mockViewController view]).andReturn(mockView);
|
||||
OCMStub([mockView window]).andReturn(mockWindow);
|
||||
OCMStub([mockWindow windowScene]).andReturn(mockWindowScene);
|
||||
OCMStub([mockWindow windowScene]).andReturn(mockWindowScene);
|
||||
OCMStub([mockWindowScene delegate]).andReturn(mockLifecycleProvider);
|
||||
OCMStub([mockLifecycleProvider sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate);
|
||||
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
|
||||
id mockWindowScene2 = OCMClassMock([UIWindowScene class]);
|
||||
|
||||
[delegate updateEnginesInScene:mockWindowScene2];
|
||||
OCMVerify(times(1), [mockLifecycleDelegate addFlutterEngine:mockEngine]);
|
||||
XCTAssertEqual(delegate.engines.count, 0.0);
|
||||
}
|
||||
|
||||
- (void)testUpdateEnginesInSceneDoesNotRemoveEngineWithNilScene {
|
||||
FlutterPluginSceneLifeCycleDelegate* delegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
[delegate addFlutterEngine:mockEngine];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
|
||||
id mockWindowScene = OCMClassMock([UIWindowScene class]);
|
||||
|
||||
[delegate updateEnginesInScene:mockWindowScene];
|
||||
XCTAssertEqual(delegate.engines.count, 1.0);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -0,0 +1,15 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSCENELIFECYCLE_TEST_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSCENELIFECYCLE_TEST_H_
|
||||
|
||||
// Category to add test-only visibility.
|
||||
@interface FlutterPluginSceneLifeCycleDelegate (Test)
|
||||
@property(nonatomic, strong) NSPointerArray* engines;
|
||||
|
||||
- (void)updateEnginesInScene:(UIScene*)scene;
|
||||
@end
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSCENELIFECYCLE_TEST_H_
|
||||
@ -5,6 +5,7 @@
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
|
||||
|
||||
#include "flutter/fml/platform/darwin/cf_utils.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h"
|
||||
|
||||
@ -241,4 +242,30 @@ static void PrintWideGamutWarningOnce() {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow*)newWindow {
|
||||
// When a FlutterView moves windows, it may also be moving scenes. Add/remove the FlutterEngine
|
||||
// from the FlutterSceneLifeCycleProvider.sceneLifeCycleDelegate if it changes scenes.
|
||||
UIWindowScene* newScene = newWindow.windowScene;
|
||||
UIWindowScene* previousScene = self.window.windowScene;
|
||||
if (newScene == previousScene) {
|
||||
return;
|
||||
}
|
||||
if ([newScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
|
||||
id<FlutterSceneLifeCycleProvider> lifeCycleProvider =
|
||||
(id<FlutterSceneLifeCycleProvider>)newScene.delegate;
|
||||
[lifeCycleProvider.sceneLifeCycleDelegate addFlutterEngine:(FlutterEngine*)self.delegate];
|
||||
}
|
||||
|
||||
if ([previousScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
|
||||
// The window, and therefore windowScene, property may be nil if the receiver does not currently
|
||||
// reside in any window. This occurs when the receiver has just been removed from its superview
|
||||
// or when the receiver has just been added to a superview that is not attached to a window.
|
||||
// Remove the engine from the previous scene if set since it is no longer in that window and
|
||||
// scene.
|
||||
id<FlutterSceneLifeCycleProvider> lifeCycleProvider =
|
||||
(id<FlutterSceneLifeCycleProvider>)previousScene.delegate;
|
||||
[lifeCycleProvider.sceneLifeCycleDelegate removeFlutterEngine:(FlutterEngine*)self.delegate];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -2,8 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifecycle_Test.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
|
||||
|
||||
FLUTTER_ASSERT_ARC
|
||||
@ -64,4 +69,155 @@ FLUTTER_ASSERT_ARC
|
||||
XCTAssertEqual(view.layer.rasterizationScale, screen.scale);
|
||||
}
|
||||
|
||||
- (void)testViewWillMoveToWindow {
|
||||
NSDictionary* mocks = [self createWindowMocks];
|
||||
FlutterView* view = (FlutterView*)mocks[@"view"];
|
||||
id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
|
||||
id mockEngine = mocks[@"mockEngine"];
|
||||
id mockWindow = mocks[@"mockWindow"];
|
||||
|
||||
[view willMoveToWindow:mockWindow];
|
||||
OCMVerify(times(1), [mockLifecycleDelegate addFlutterEngine:mockEngine]);
|
||||
XCTAssertEqual(lifecycleDelegate.engines.count, 1.0);
|
||||
}
|
||||
|
||||
- (void)testViewWillMoveToSameWindow {
|
||||
NSDictionary* mocks = [self createWindowMocks];
|
||||
FlutterView* view = (FlutterView*)mocks[@"view"];
|
||||
id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
|
||||
id mockEngine = mocks[@"mockEngine"];
|
||||
id mockWindow = mocks[@"mockWindow"];
|
||||
|
||||
[view willMoveToWindow:mockWindow];
|
||||
[view willMoveToWindow:mockWindow];
|
||||
|
||||
OCMVerify(times(2), [mockLifecycleDelegate addFlutterEngine:mockEngine]);
|
||||
XCTAssertEqual(lifecycleDelegate.engines.count, 1.0);
|
||||
}
|
||||
|
||||
- (void)testMultipleViewsWillMoveToSameWindow {
|
||||
NSDictionary* mocks = [self createWindowMocks];
|
||||
FlutterView* view1 = (FlutterView*)mocks[@"view"];
|
||||
id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
|
||||
id mockEngine1 = mocks[@"mockEngine"];
|
||||
id mockWindow1 = mocks[@"mockWindow"];
|
||||
|
||||
id mockEngine2 = OCMClassMock([FlutterEngine class]);
|
||||
FlutterView* view2 = [[FlutterView alloc] initWithDelegate:mockEngine2
|
||||
opaque:NO
|
||||
enableWideGamut:NO];
|
||||
|
||||
[view1 willMoveToWindow:mockWindow1];
|
||||
[view2 willMoveToWindow:mockWindow1];
|
||||
[view1 willMoveToWindow:mockWindow1];
|
||||
OCMVerify(times(2), [mockLifecycleDelegate addFlutterEngine:mockEngine1]);
|
||||
OCMVerify(times(1), [mockLifecycleDelegate addFlutterEngine:mockEngine2]);
|
||||
XCTAssertEqual(lifecycleDelegate.engines.count, 2.0);
|
||||
}
|
||||
|
||||
- (void)testMultipleViewsWillMoveToDifferentWindow {
|
||||
NSDictionary* mocks = [self createWindowMocks];
|
||||
FlutterView* view1 = (FlutterView*)mocks[@"view"];
|
||||
id mockLifecycleDelegate1 = mocks[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate1 =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
|
||||
id mockEngine1 = mocks[@"mockEngine"];
|
||||
id mockWindow1 = mocks[@"mockWindow"];
|
||||
|
||||
NSDictionary* mocks2 = [self createWindowMocks];
|
||||
FlutterView* view2 = (FlutterView*)mocks2[@"view"];
|
||||
id mockLifecycleDelegate2 = mocks2[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate2 =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks2[@"lifecycleDelegate"];
|
||||
id mockEngine2 = mocks2[@"mockEngine"];
|
||||
id mockWindow2 = mocks2[@"mockWindow"];
|
||||
|
||||
[view1 willMoveToWindow:mockWindow1];
|
||||
[view2 willMoveToWindow:mockWindow2];
|
||||
[view1 willMoveToWindow:mockWindow1];
|
||||
OCMVerify(times(2), [mockLifecycleDelegate1 addFlutterEngine:mockEngine1]);
|
||||
OCMVerify(times(1), [mockLifecycleDelegate2 addFlutterEngine:mockEngine2]);
|
||||
XCTAssertEqual(lifecycleDelegate1.engines.count, 1.0);
|
||||
XCTAssertEqual(lifecycleDelegate2.engines.count, 1.0);
|
||||
}
|
||||
|
||||
- (void)testNilWindowForViewWhenNoPrevious {
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
FlutterView* view = [[FlutterView alloc] initWithDelegate:mockEngine
|
||||
opaque:NO
|
||||
enableWideGamut:NO];
|
||||
[view willMoveToWindow:nil];
|
||||
}
|
||||
|
||||
- (void)testNilWindowForViewWhenPrevious {
|
||||
NSDictionary* mocks = [self createWindowMocks];
|
||||
FlutterView* view = (FlutterView*)mocks[@"view"];
|
||||
id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
|
||||
id mockEngine = mocks[@"mockEngine"];
|
||||
id mockWindow = mocks[@"mockWindow"];
|
||||
|
||||
id mockView = OCMPartialMock(view);
|
||||
OCMStub([mockView window]).andReturn(mockWindow);
|
||||
|
||||
[mockView willMoveToWindow:nil];
|
||||
|
||||
OCMVerify(times(1), [mockLifecycleDelegate removeFlutterEngine:mockEngine]);
|
||||
XCTAssertEqual(lifecycleDelegate.engines.count, 0.0);
|
||||
}
|
||||
|
||||
- (void)testViewWillMoveToWindowWhenPreviousEqualsNew {
|
||||
NSDictionary* mocks = [self createWindowMocks];
|
||||
FlutterView* view = (FlutterView*)mocks[@"view"];
|
||||
id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
|
||||
(FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
|
||||
id mockEngine = mocks[@"mockEngine"];
|
||||
id mockWindow = mocks[@"mockWindow"];
|
||||
|
||||
id mockView = OCMPartialMock(view);
|
||||
OCMStub([mockView window]).andReturn(mockWindow);
|
||||
|
||||
[mockView willMoveToWindow:mockWindow];
|
||||
|
||||
OCMVerify(times(0), [mockLifecycleDelegate addFlutterEngine:mockEngine]);
|
||||
OCMVerify(times(0), [mockLifecycleDelegate removeFlutterEngine:[OCMArg any]]);
|
||||
XCTAssertEqual(lifecycleDelegate.engines.count, 0.0);
|
||||
}
|
||||
|
||||
- (NSDictionary*)createWindowMocks {
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
FlutterView* view = [[FlutterView alloc] initWithDelegate:mockEngine
|
||||
opaque:NO
|
||||
enableWideGamut:NO];
|
||||
id mockWindow = OCMClassMock([UIWindow class]);
|
||||
id mockWindowScene = OCMClassMock([UIWindowScene class]);
|
||||
|
||||
FlutterSceneDelegate* sceneDelegate = [[FlutterSceneDelegate alloc] init];
|
||||
id mockSceneDelegate = OCMPartialMock(sceneDelegate);
|
||||
|
||||
FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
|
||||
[[FlutterPluginSceneLifeCycleDelegate alloc] init];
|
||||
id mockLifecycleDelegate = OCMPartialMock(lifecycleDelegate);
|
||||
|
||||
OCMStub([mockWindow windowScene]).andReturn(mockWindowScene);
|
||||
OCMStub([mockWindowScene delegate]).andReturn(mockSceneDelegate);
|
||||
OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate);
|
||||
|
||||
return @{
|
||||
@"view" : view,
|
||||
@"mockLifecycleDelegate" : mockLifecycleDelegate,
|
||||
@"lifecycleDelegate" : lifecycleDelegate,
|
||||
@"mockEngine" : mockEngine,
|
||||
@"mockWindow" : mockWindow,
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -75,6 +75,8 @@
|
||||
689EC1E2281B30D3008FEB58 /* FlutterSpellCheckPluginTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterSpellCheckPluginTest.mm; sourceTree = "<group>"; };
|
||||
68B6091227F62F990036AC78 /* VsyncWaiterIosTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VsyncWaiterIosTest.mm; sourceTree = "<group>"; };
|
||||
7802C4F12E67A0CC002C7D6D /* FlutterSceneDelegateTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FlutterSceneDelegateTest.m; sourceTree = "<group>"; };
|
||||
78136AF72E68D14D00900DCE /* FlutterSceneLifecycleTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterSceneLifecycleTest.mm; sourceTree = "<group>"; };
|
||||
78A9D2512E6F56DA00BEE2FC /* FlutterViewTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterViewTest.mm; sourceTree = "<group>"; };
|
||||
78E4ED342D88A77C00FD954E /* FlutterSharedApplicationTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterSharedApplicationTest.mm; sourceTree = "<group>"; };
|
||||
D2D361A52B234EAC0018964E /* FlutterMetalLayerTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterMetalLayerTest.mm; sourceTree = "<group>"; };
|
||||
F7521D7226BB671E005F15C5 /* libios_test_flutter.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libios_test_flutter.dylib; path = "../../../../out/$(FLUTTER_ENGINE)/libios_test_flutter.dylib"; sourceTree = "<group>"; };
|
||||
@ -105,6 +107,7 @@
|
||||
0AC232E924BA71D300A85907 /* Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78136AF72E68D14D00900DCE /* FlutterSceneLifecycleTest.mm */,
|
||||
7802C4F12E67A0CC002C7D6D /* FlutterSceneDelegateTest.m */,
|
||||
78E4ED342D88A77C00FD954E /* FlutterSharedApplicationTest.mm */,
|
||||
F76A3A892BE48F2F00A654F1 /* FlutterPlatformViewsTest.mm */,
|
||||
@ -114,6 +117,7 @@
|
||||
F7A3FDE026B9E0A300EADD61 /* FlutterAppDelegateTest.mm */,
|
||||
0AC232F424BA71D300A85907 /* SemanticsObjectTest.mm */,
|
||||
0AC232F724BA71D300A85907 /* FlutterEngineTest.mm */,
|
||||
78A9D2512E6F56DA00BEE2FC /* FlutterViewTest.mm */,
|
||||
0AC2330324BA71D300A85907 /* accessibility_bridge_test.mm */,
|
||||
0AC2330B24BA71D300A85907 /* FlutterTextInputPluginTest.mm */,
|
||||
0AC2331024BA71D300A85907 /* connection_collection_test.mm */,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user