Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macOS support for plugin value publishing #45502

Merged
merged 1 commit into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ FLUTTER_DARWIN_EXPORT
- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory
withId:(nonnull NSString*)factoryId;

/**
* Publishes a value for external use of the plugin.
*
* Plugins may publish a single value, such as an instance of the
* plugin's main class, for situations where external control or
* interaction is needed.
*
* The published value will be available from the `FlutterPluginRegistry`.
* Repeated calls overwrite any previous publication.
*
* @param value The value to be published.
*/
- (void)publish:(nonnull NSObject*)value;

/**
* Returns the file name for the given asset.
* The returned file name can be used to access the asset in the application's main bundle.
Expand Down Expand Up @@ -119,4 +133,14 @@ FLUTTER_DARWIN_EXPORT
*/
- (nonnull id<FlutterPluginRegistrar>)registrarForPlugin:(nonnull NSString*)pluginKey;

/**
* Returns a value published by the specified plugin.
*
* @param pluginKey The unique key identifying the plugin.
* @return An object published by the plugin, if any. Will be `NSNull` if
* nothing has been published. Will be `nil` if the plugin has not been
* registered.
*/
- (nullable NSObject*)valuePublishedByPlugin:(nonnull NSString*)pluginKey;

@end
43 changes: 40 additions & 3 deletions shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h"

@class FlutterEngineRegistrar;

NSString* const kFlutterPlatformChannel = @"flutter/platform";
NSString* const kFlutterSettingsChannel = @"flutter/settings";
NSString* const kFlutterLifecycleChannel = @"flutter/lifecycle";
Expand Down Expand Up @@ -95,6 +97,12 @@ @interface FlutterEngine () <FlutterBinaryMessenger>
*/
@property(nonatomic, strong) NSPointerArray* pluginAppDelegates;

/**
* All registrars returned from registrarForPlugin:
*/
@property(nonatomic, readonly)
NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* pluginRegistrars;

- (nullable FlutterViewController*)viewControllerForId:(FlutterViewId)viewId;

/**
Expand Down Expand Up @@ -274,12 +282,19 @@ @interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
- (instancetype)initWithPlugin:(nonnull NSString*)pluginKey
flutterEngine:(nonnull FlutterEngine*)flutterEngine;

- (NSView*)viewForId:(FlutterViewId)viewId;
- (nullable NSView*)viewForId:(FlutterViewId)viewId;

/**
* The value published by this plugin, or NSNull if nothing has been published.
*
* The unusual NSNull is for the documented behavior of valuePublishedByPlugin:.
*/
@property(nonatomic, readonly, nonnull) NSObject* publishedValue;
@end

@implementation FlutterEngineRegistrar {
NSString* _pluginKey;
FlutterEngine* _flutterEngine;
__weak FlutterEngine* _flutterEngine;
stuartmorgan marked this conversation as resolved.
Show resolved Hide resolved
}

@dynamic view;
Expand All @@ -289,6 +304,7 @@ - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine
if (self) {
_pluginKey = [pluginKey copy];
_flutterEngine = flutterEngine;
_publishedValue = [NSNull null];
}
return self;
}
Expand Down Expand Up @@ -340,6 +356,10 @@ - (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)facto
[[_flutterEngine platformViewController] registerViewFactory:factory withId:factoryId];
}

- (void)publish:(NSObject*)value {
_publishedValue = value;
}

- (NSString*)lookupKeyForAsset:(NSString*)asset {
return [FlutterDartProject lookupKeyForAsset:asset];
}
Expand Down Expand Up @@ -438,6 +458,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix
_messengerHandlers = [[NSMutableDictionary alloc] init];
_binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
_pluginAppDelegates = [NSPointerArray weakObjectsPointerArray];
_pluginRegistrars = [[NSMutableDictionary alloc] init];
_currentMessengerConnection = 1;
_allowHeadlessExecution = allowHeadlessExecution;
_semanticsEnabled = NO;
Expand Down Expand Up @@ -494,6 +515,11 @@ - (void)dealloc {
}
}
}
// Clear any published values, just in case a plugin has created a retain cycle with the
// registrar.
for (NSString* pluginName in _pluginRegistrars) {
[_pluginRegistrars[pluginName] publish:[NSNull null]];
}
@synchronized(_isResponseValid) {
[_isResponseValid removeAllObjects];
[_isResponseValid addObject:@NO];
Expand Down Expand Up @@ -1334,7 +1360,18 @@ - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
#pragma mark - FlutterPluginRegistry

- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
return [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];
id<FlutterPluginRegistrar> registrar = self.pluginRegistrars[pluginName];
if (!registrar) {
FlutterEngineRegistrar* registrarImpl =
[[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];
self.pluginRegistrars[pluginName] = registrarImpl;
registrar = registrarImpl;
}
return registrar;
}

- (nullable NSObject*)valuePublishedByPlugin:(NSString*)pluginName {
return self.pluginRegistrars[pluginName].publishedValue;
}

#pragma mark - FlutterTextureRegistrar
Expand Down
50 changes: 50 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,56 @@ + (void)registerWithRegistrar:(id<FlutterPluginRegistrar>)registrar {
EXPECT_EQ(weakEngine, nil);
}

TEST_F(FlutterEngineTest, PublishedValueNilForUnknownPlugin) {
NSString* fixtures = @(flutter::testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
project:project
allowHeadlessExecution:YES];

EXPECT_EQ([engine valuePublishedByPlugin:@"NoSuchPlugin"], nil);
}

TEST_F(FlutterEngineTest, PublishedValueNSNullIfNoPublishedValue) {
NSString* fixtures = @(flutter::testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
project:project
allowHeadlessExecution:YES];
NSString* pluginName = @"MyPlugin";
// Request the registarar to register the plugin as existing.
[engine registrarForPlugin:pluginName];

// The documented behavior is that a plugin that exists but hasn't published
// anything returns NSNull, rather than nil, as on iOS.
EXPECT_EQ([engine valuePublishedByPlugin:pluginName], [NSNull null]);
}

TEST_F(FlutterEngineTest, PublishedValueReturnsLastPublished) {
NSString* fixtures = @(flutter::testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
project:project
allowHeadlessExecution:YES];
NSString* pluginName = @"MyPlugin";
id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:pluginName];

NSString* firstValue = @"A published value";
NSArray* secondValue = @[ @"A different published value" ];

[registrar publish:firstValue];
EXPECT_EQ([engine valuePublishedByPlugin:pluginName], firstValue);

[registrar publish:secondValue];
EXPECT_EQ([engine valuePublishedByPlugin:pluginName], secondValue);
}

// If a channel overrides a previous channel with the same name, cleaning
// the previous channel should not affect the new channel.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,10 @@ - (void)viewDidReshape:(NSView*)view {
return [_engine registrarForPlugin:pluginName];
}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
return [_engine valuePublishedByPlugin:pluginKey];
}

#pragma mark - FlutterKeyboardViewDelegate

- (void)sendKeyEvent:(const FlutterKeyEvent&)event
Expand Down