Skip to content

Commit 7a47987

Browse files
committed
Revert "Revert "[macOS] Formalize FlutterViewController's initialization flow, and prohibit replacing (flutter#38981)" (flutter#39144)"
This reverts commit 36db005.
1 parent 36db005 commit 7a47987

14 files changed

+282
-301
lines changed

shell/platform/darwin/macos/framework/Headers/FlutterEngine.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#import <Foundation/Foundation.h>
99

10+
#include <stdint.h>
11+
1012
#import "FlutterBinaryMessenger.h"
1113
#import "FlutterDartProject.h"
1214
#import "FlutterMacros.h"
@@ -15,6 +17,17 @@
1517

1618
// TODO: Merge this file with the iOS FlutterEngine.h.
1719

20+
/**
21+
* The view ID for APIs that don't support multi-view.
22+
*
23+
* Some single-view APIs will eventually be replaced by their multi-view
24+
* variant. During the deprecation period, the single-view APIs will coexist with
25+
* and work with the multi-view APIs as if the other views don't exist. For
26+
* backward compatibility, single-view APIs will always operate the view with
27+
* this ID. Also, the first view assigned to the engine will also have this ID.
28+
*/
29+
extern const uint64_t kFlutterDefaultViewId;
30+
1831
@class FlutterViewController;
1932

2033
/**
@@ -67,6 +80,15 @@ FLUTTER_DARWIN_EXPORT
6780
*
6881
* The default view always has ID kFlutterDefaultViewId, and is the view
6982
* operated by the APIs that do not have a view ID specified.
83+
*
84+
* Setting this field from nil to a non-nil view controller also updates
85+
* the view controller's engine and ID.
86+
*
87+
* Setting this field from non-nil to nil will terminate the engine if
88+
* allowHeadlessExecution is NO.
89+
*
90+
* Setting this field from non-nil to a different non-nil FlutterViewController
91+
* is prohibited and will throw an assertion error.
7092
*/
7193
@property(nonatomic, nullable, weak) FlutterViewController* viewController;
7294

shell/platform/darwin/macos/framework/Headers/FlutterViewController.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ typedef NS_ENUM(NSInteger, FlutterMouseTrackingMode) {
2525

2626
/**
2727
* Controls a view that displays Flutter content and manages input.
28+
*
29+
* A FlutterViewController works with a FlutterEngine. Upon creation, the view
30+
* controller is always added to an engine, either a given engine, or it implicitly
31+
* creates an engine and add itself to that engine.
32+
*
33+
* The FlutterEngine assigns each view controller attached to it a unique ID.
34+
* Each view controller corresponds to a view, and the ID is used by the framework
35+
* to specify which view to operate.
36+
*
37+
* A FlutterViewController can also be unattached to an engine after it is manually
38+
* unset from the engine, or transiently during the initialization process.
39+
* An unattached view controller is invalid. Whether the view controller is attached
40+
* can be queried using FlutterViewController#attached.
41+
*
42+
* The FlutterViewController strongly references the FlutterEngine, while
43+
* the engine weakly the view controller. When a FlutterViewController is deallocated,
44+
* it automatically removes itself from its attached engine. When a FlutterEngine
45+
* has no FlutterViewControllers attached, it might shut down itself or not depending
46+
* on its configuration.
2847
*/
2948
FLUTTER_DARWIN_EXPORT
3049
@interface FlutterViewController : NSViewController <FlutterPluginRegistry>
@@ -34,6 +53,16 @@ FLUTTER_DARWIN_EXPORT
3453
*/
3554
@property(nonatomic, nonnull, readonly) FlutterEngine* engine;
3655

56+
/**
57+
* The identifier for this view controller.
58+
*
59+
* The ID is assigned by FlutterEngine when the view controller is attached.
60+
*
61+
* If the view controller is unattached (see FlutterViewController#attached),
62+
* reading this property throws an assertion.
63+
*/
64+
@property(nonatomic, readonly) uint64_t id;
65+
3766
/**
3867
* The style of mouse tracking to use for the view. Defaults to
3968
* FlutterMouseTrackingModeInKeyWindow.
@@ -43,6 +72,12 @@ FLUTTER_DARWIN_EXPORT
4372
/**
4473
* Initializes a controller that will run the given project.
4574
*
75+
* In this initializer, this controller creates an engine, and is attached to
76+
* that engine as the default controller. In this way, this controller can not
77+
* be set to other engines. This initializer is suitable for the first Flutter
78+
* view controller of the app. To use the controller with an existing engine,
79+
* use initWithEngine:nibName:bundle: instead.
80+
*
4681
* @param project The project to run in this view controller. If nil, a default `FlutterDartProject`
4782
* will be used.
4883
*/
@@ -56,7 +91,8 @@ FLUTTER_DARWIN_EXPORT
5691
/**
5792
* Initializes this FlutterViewController with the specified `FlutterEngine`.
5893
*
59-
* The initialized viewcontroller will attach itself to the engine as part of this process.
94+
* This initializer is suitable for both the first Flutter view controller and
95+
* the following ones of the app.
6096
*
6197
* @param engine The `FlutterEngine` instance to attach to. Cannot be nil.
6298
* @param nibName The NIB name to initialize this controller with.
@@ -65,6 +101,12 @@ FLUTTER_DARWIN_EXPORT
65101
- (nonnull instancetype)initWithEngine:(nonnull FlutterEngine*)engine
66102
nibName:(nullable NSString*)nibName
67103
bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER;
104+
105+
/**
106+
* Return YES if the view controller is attached to an engine.
107+
*/
108+
- (BOOL)attached;
109+
68110
/**
69111
* Invoked by the engine right before the engine is restarted.
70112
*

shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,22 @@ @implementation AccessibilityBridgeTestEngine
5151
namespace {
5252

5353
// Returns an engine configured for the text fixture resource configuration.
54-
FlutterEngine* CreateTestEngine() {
54+
FlutterViewController* CreateTestViewController() {
5555
NSString* fixtures = @(testing::GetFixturesPath());
5656
FlutterDartProject* project = [[FlutterDartProject alloc]
5757
initWithAssetsPath:fixtures
5858
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
59-
return [[AccessibilityBridgeTestEngine alloc] initWithName:@"test"
60-
project:project
61-
allowHeadlessExecution:true];
59+
FlutterEngine* engine = [[AccessibilityBridgeTestEngine alloc] initWithName:@"test"
60+
project:project
61+
allowHeadlessExecution:true];
62+
return [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
6263
}
6364
} // namespace
6465

6566
TEST(AccessibilityBridgeMacTest, sendsAccessibilityCreateNotificationToWindowOfFlutterView) {
66-
FlutterEngine* engine = CreateTestEngine();
67-
NSString* fixtures = @(testing::GetFixturesPath());
68-
FlutterDartProject* project = [[FlutterDartProject alloc]
69-
initWithAssetsPath:fixtures
70-
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
71-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
67+
FlutterViewController* viewController = CreateTestViewController();
68+
FlutterEngine* engine = viewController.engine;
7269
[viewController loadView];
73-
[engine setViewController:viewController];
7470

7571
NSWindow* expectedTarget = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
7672
styleMask:NSBorderlessWindowMask
@@ -122,14 +118,9 @@ @implementation AccessibilityBridgeTestEngine
122118
}
123119

124120
TEST(AccessibilityBridgeMacTest, doesNotSendAccessibilityCreateNotificationWhenHeadless) {
125-
FlutterEngine* engine = CreateTestEngine();
126-
NSString* fixtures = @(testing::GetFixturesPath());
127-
FlutterDartProject* project = [[FlutterDartProject alloc]
128-
initWithAssetsPath:fixtures
129-
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
130-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
121+
FlutterViewController* viewController = CreateTestViewController();
122+
FlutterEngine* engine = viewController.engine;
131123
[viewController loadView];
132-
[engine setViewController:viewController];
133124
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
134125
// can query semantics information from.
135126
engine.semanticsEnabled = YES;
@@ -173,15 +164,9 @@ @implementation AccessibilityBridgeTestEngine
173164
}
174165

175166
TEST(AccessibilityBridgeMacTest, doesNotSendAccessibilityCreateNotificationWhenNoWindow) {
176-
FlutterEngine* engine = CreateTestEngine();
177-
// Create a view controller without attaching it to a window.
178-
NSString* fixtures = @(testing::GetFixturesPath());
179-
FlutterDartProject* project = [[FlutterDartProject alloc]
180-
initWithAssetsPath:fixtures
181-
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
182-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
167+
FlutterViewController* viewController = CreateTestViewController();
168+
FlutterEngine* engine = viewController.engine;
183169
[viewController loadView];
184-
[engine setViewController:viewController];
185170

186171
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
187172
// can query semantics information from.

shell/platform/darwin/macos/framework/Source/FlutterCompositor.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
#include "flutter/fml/macros.h"
1212
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
13-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
1413
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h"
1514
#include "flutter/shell/platform/embedder/embedder.h"
1615

shell/platform/darwin/macos/framework/Source/FlutterEngine.mm

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h"
2020
#include "flutter/shell/platform/embedder/embedder.h"
2121

22+
const uint64_t kFlutterDefaultViewId = 0;
23+
2224
/**
2325
* Constructs and returns a FlutterLocale struct corresponding to |locale|, which must outlive
2426
* the returned struct.
@@ -80,6 +82,8 @@ @interface FlutterEngine () <FlutterBinaryMessenger>
8082
*/
8183
@property(nonatomic, strong) NSMutableArray<NSNumber*>* isResponseValid;
8284

85+
- (nullable FlutterViewController*)viewControllerForId:(uint64_t)viewId;
86+
8387
/**
8488
* Sends the list of user-preferred locales to the Flutter engine.
8589
*/
@@ -213,8 +217,6 @@ @implementation FlutterEngine {
213217
// when the engine is destroyed.
214218
std::unique_ptr<flutter::FlutterCompositor> _macOSCompositor;
215219

216-
FlutterViewEngineProvider* _viewProvider;
217-
218220
// FlutterCompositor is copied and used in embedder.cc.
219221
FlutterCompositor _compositor;
220222

@@ -248,7 +250,6 @@ - (instancetype)initWithName:(NSString*)labelPrefix
248250
_currentMessengerConnection = 1;
249251
_allowHeadlessExecution = allowHeadlessExecution;
250252
_semanticsEnabled = NO;
251-
_viewProvider = [[FlutterViewEngineProvider alloc] initWithEngine:self];
252253
_isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
253254
[_isResponseValid addObject:@YES];
254255

@@ -411,29 +412,41 @@ - (void)loadAOTData:(NSString*)assetsDir {
411412
}
412413

413414
- (void)setViewController:(FlutterViewController*)controller {
414-
if (_viewController != controller) {
415+
if (_viewController == controller) {
416+
// From nil to nil, or from non-nil to the same controller.
417+
return;
418+
}
419+
if (_viewController == nil && controller != nil) {
420+
// From nil to non-nil.
421+
NSAssert(controller.engine == nil,
422+
@"Failed to set view controller to the engine: "
423+
@"The given FlutterViewController is already attached to an engine %@. "
424+
@"If you wanted to create an FlutterViewController and set it to an existing engine, "
425+
@"you should create it with init(engine:, nibName, bundle:) instead.",
426+
controller.engine);
415427
_viewController = controller;
416-
417-
if (_semanticsEnabled && _bridge) {
418-
_bridge->UpdateDefaultViewController(_viewController);
419-
}
420-
421-
if (!controller && !_allowHeadlessExecution) {
428+
[_viewController attachToEngine:self withId:kFlutterDefaultViewId];
429+
} else if (_viewController != nil && controller == nil) {
430+
// From non-nil to nil.
431+
[_viewController detachFromEngine];
432+
_viewController = nil;
433+
if (!_allowHeadlessExecution) {
422434
[self shutDownEngine];
423435
}
436+
} else {
437+
// From non-nil to a different non-nil view controller.
438+
NSAssert(NO,
439+
@"Failed to set view controller to the engine: "
440+
@"The engine already has a default view controller %@. "
441+
@"If you wanted to make the default view render in a different window, "
442+
@"you should attach the current view controller to the window instead.",
443+
_viewController);
424444
}
425445
}
426446

427447
- (FlutterCompositor*)createFlutterCompositor {
428-
// TODO(richardjcai): Add support for creating a FlutterCompositor
429-
// with a nil _viewController for headless engines.
430-
// https://github.com/flutter/flutter/issues/71606
431-
if (!_viewController) {
432-
return nil;
433-
}
434-
435-
_macOSCompositor =
436-
std::make_unique<flutter::FlutterCompositor>(_viewProvider, _platformViewController);
448+
_macOSCompositor = std::make_unique<flutter::FlutterCompositor>(
449+
[[FlutterViewEngineProvider alloc] initWithEngine:self], _platformViewController);
437450

438451
_compositor = {};
439452
_compositor.struct_size = sizeof(FlutterCompositor);
@@ -539,10 +552,10 @@ - (nonnull NSString*)executableName {
539552
}
540553

541554
- (void)updateWindowMetrics {
542-
if (!_engine || !_viewController.viewLoaded) {
555+
if (!_engine || !self.viewController.viewLoaded) {
543556
return;
544557
}
545-
NSView* view = _viewController.flutterView;
558+
NSView* view = self.viewController.flutterView;
546559
CGRect scaledBounds = [view convertRectToBacking:view.bounds];
547560
CGSize scaledSize = scaledBounds.size;
548561
double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width;
@@ -603,6 +616,17 @@ - (FlutterPlatformViewController*)platformViewController {
603616

604617
#pragma mark - Private methods
605618

619+
- (FlutterViewController*)viewControllerForId:(uint64_t)viewId {
620+
// TODO(dkwingsmt): The engine only supports single-view, therefore it
621+
// only processes the default ID. After the engine supports multiple views,
622+
// this method should be able to return the view for any IDs.
623+
NSAssert(viewId == kFlutterDefaultViewId, @"Unexpected view ID %llu", viewId);
624+
if (viewId == kFlutterDefaultViewId) {
625+
return _viewController;
626+
}
627+
return nil;
628+
}
629+
606630
- (void)sendUserLocales {
607631
if (!self.running) {
608632
return;
@@ -679,9 +703,7 @@ - (void)shutDownEngine {
679703
return;
680704
}
681705

682-
if (_viewController && _viewController.flutterView) {
683-
[_viewController.flutterView shutdown];
684-
}
706+
[self.viewController.flutterView shutdown];
685707

686708
FlutterEngineResult result = _embedderAPI.Deinitialize(_engine);
687709
if (result != kSuccess) {

0 commit comments

Comments
 (0)