diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h index 0c427c247d4..126fb00bc6a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h @@ -27,7 +27,20 @@ FLUTTER_DARWIN_EXPORT @interface FlutterAppDelegate : UIResponder -@property(strong, nonatomic) UIWindow* window; +@property(nonatomic, strong, nullable) UIWindow* window; + +/** + * The `FlutterPluginRegistrant` that will be used when FlutterViewControllers + * are instantiated from nibs. + * + * The `FlutterAppDelegate` itself can be passed in without creating a retain + * cycle. + * + * This was introduced to help users migrate code from the FlutterAppDelegate + * when UISceneDelegate was adopted. Using + * FlutterViewController.pluginRegistrant should be preferred. + */ +@property(nonatomic, strong, nullable) NSObject* pluginRegistrant; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index 8ab46cfe80a..532d5262d60 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -425,6 +425,27 @@ typedef enum { - (nullable NSObject*)valuePublishedByPlugin:(NSString*)pluginKey; @end +#pragma mark - +/** + * The target of registration of plugins. + * + * This often is hooked up to the GeneratedPluginRegistrant which is + * automatically generated by Flutter for the dependencies listed in the + * project. + */ +@protocol FlutterPluginRegistrant +@required +/** + * Register all the plugins for the registrant. + * + * This will be called after a FlutterEngine has been instantiated, the registry + * will connect any plugins to that engine. + * + * @param registry The registry where plugins will be registered. + */ +- (void)registerWithRegistry:(NSObject*)registry; +@end + #pragma mark - /** * Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index e41565c3b1f..ede5b1aea84 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -254,6 +254,16 @@ FLUTTER_DARWIN_EXPORT */ @property(nonatomic, readonly) BOOL engineAllowHeadlessExecution; +/** + * The plugin registrant that will be executed when the FlutterViewController is + * created with a NIB. + * + * This is only necessary when working with NIBs (XIBs and Storyboards). When + * programatically creating FlutterViewControllers, plugins can be registered + * directly. + */ +@property(nonatomic, weak) IBOutlet NSObject* pluginRegistrant; + @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index a30dd9a6b19..e15a0a059fc 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -20,7 +20,10 @@ static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification"; static NSString* const kBackgroundFetchCapatibility = @"fetch"; static NSString* const kRestorationStateAppModificationKey = @"mod-date"; -@interface FlutterAppDelegate () +@interface FlutterAppDelegate () { + __weak NSObject* _weakRegistrant; + NSObject* _strongRegistrant; +} @property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void); @property(nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate; @end @@ -228,6 +231,26 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date"; #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController +- (NSObject*)pluginRegistrant { + if (_weakRegistrant) { + return _weakRegistrant; + } + if (_strongRegistrant) { + return _strongRegistrant; + } + return nil; +} + +- (void)setPluginRegistrant:(NSObject*)pluginRegistrant { + if (pluginRegistrant == (id)self) { + _weakRegistrant = pluginRegistrant; + _strongRegistrant = nil; + } else { + _weakRegistrant = nil; + _strongRegistrant = pluginRegistrant; + } +} + - (NSObject*)registrarForPlugin:(NSString*)pluginKey { FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; if (flutterRootViewController) { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm index df9e7c9b43a..184c1c6010d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm @@ -198,4 +198,20 @@ FLUTTER_ASSERT_ARC completionHandler:[OCMArg any]]); } +- (void)testSetGetPluginRegistrant { + id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant)); + self.appDelegate.pluginRegistrant = mockRegistrant; + XCTAssertEqual(self.appDelegate.pluginRegistrant, mockRegistrant); +} + +- (void)testSetGetPluginRegistrantSelf { + __weak FlutterAppDelegate* appDelegate = self.appDelegate; + @autoreleasepool { + appDelegate.pluginRegistrant = (id)appDelegate; + self.appDelegate = nil; + } + // A retain cycle would keep this alive. + XCTAssertNil(appDelegate); +} + @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 7139e13983c..71aca23a407 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -247,6 +247,9 @@ typedef struct MouseState { if (!self.engine) { [self sharedSetupWithProject:nil initialRoute:nil]; } + if (self.pluginRegistrant) { + [self.pluginRegistrant registerWithRegistry:self]; + } } - (instancetype)init { @@ -281,6 +284,13 @@ typedef struct MouseState { // Eliminate method calls in initializers and dealloc. [self loadDefaultSplashScreenView]; [self performCommonViewControllerInitialization]; + + if ([FlutterSharedApplication.application.delegate + respondsToSelector:@selector(pluginRegistrant)]) { + NSObject* pluginRegistrant = + [FlutterSharedApplication.application.delegate performSelector:@selector(pluginRegistrant)]; + [pluginRegistrant registerWithRegistry:self]; + } } - (BOOL)isViewOpaque { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 2849be7c5f3..ac92f4bddef 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -22,6 +22,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" #import "flutter/shell/platform/embedder/embedder.h" +#import "flutter/testing/ios/IosUnitTests/App/AppDelegate.h" #import "flutter/third_party/spring_animation/spring_animation.h" FLUTTER_ASSERT_ARC @@ -2471,4 +2472,22 @@ extern NSNotificationName const FlutterViewControllerWillDealloc; [mockVC stopMocking]; } +- (void)testPluginRegistrant { + id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant)); + FlutterViewController* viewController = [[FlutterViewController alloc] init]; + viewController.pluginRegistrant = mockRegistrant; + [viewController awakeFromNib]; + OCMVerify([mockRegistrant registerWithRegistry:viewController]); +} + +- (void)testAppDelegatePluginRegistrant { + id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant)); + id appDelegate = [[UIApplication sharedApplication] delegate]; + XCTAssertTrue([appDelegate respondsToSelector:@selector(setPluginRegistrant:)]); + [appDelegate setPluginRegistrant:mockRegistrant]; + FlutterViewController* viewController = [[FlutterViewController alloc] init]; + [appDelegate setPluginRegistrant:nil]; + OCMVerify([mockRegistrant registerWithRegistry:viewController]); +} + @end diff --git a/engine/src/flutter/testing/ios/IosUnitTests/App/AppDelegate.h b/engine/src/flutter/testing/ios/IosUnitTests/App/AppDelegate.h index c987bb4da9e..530b65ecb9a 100644 --- a/engine/src/flutter/testing/ios/IosUnitTests/App/AppDelegate.h +++ b/engine/src/flutter/testing/ios/IosUnitTests/App/AppDelegate.h @@ -7,9 +7,14 @@ #import +@protocol FlutterPluginRegistrant; + @interface AppDelegate : UIResponder -@property(strong, nonatomic) UIWindow* window; +@property(nonatomic, strong, nullable) UIWindow* window; + +// A mirror of the FlutterAppDelegate API for integration testing. +@property(nonatomic, strong, nullable) NSObject* pluginRegistrant; @end