Skip to content

Commit 0e536eb

Browse files
gaaclarkevashworth
andauthored
Introduces FlutterPluginRegistrant protocol. (#169399)
design doc: https://docs.google.com/document/d/1ZfcQOs-UKRa9jsFG84-MTFeibZTLKCvPQLxF2eskx44/edit?tab=t.0 issue: flutter/flutter#167267 This provides the proper long term API for registering plugins in lieu of `application:didFinishLaunching:withOptions:` no longer being a viable place. ## 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]. <!-- 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 --------- Co-authored-by: Victoria Ashworth <[email protected]>
1 parent 5d013c7 commit 0e536eb

8 files changed

Lines changed: 120 additions & 3 deletions

File tree

engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,20 @@ FLUTTER_DARWIN_EXPORT
2727
@interface FlutterAppDelegate
2828
: UIResponder <UIApplicationDelegate, FlutterPluginRegistry, FlutterAppLifeCycleProvider>
2929

30-
@property(strong, nonatomic) UIWindow* window;
30+
@property(nonatomic, strong, nullable) UIWindow* window;
31+
32+
/**
33+
* The `FlutterPluginRegistrant` that will be used when FlutterViewControllers
34+
* are instantiated from nibs.
35+
*
36+
* The `FlutterAppDelegate` itself can be passed in without creating a retain
37+
* cycle.
38+
*
39+
* This was introduced to help users migrate code from the FlutterAppDelegate
40+
* when UISceneDelegate was adopted. Using
41+
* FlutterViewController.pluginRegistrant should be preferred.
42+
*/
43+
@property(nonatomic, strong, nullable) NSObject<FlutterPluginRegistrant>* pluginRegistrant;
3144

3245
@end
3346

engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,27 @@ typedef enum {
425425
- (nullable NSObject*)valuePublishedByPlugin:(NSString*)pluginKey;
426426
@end
427427

428+
#pragma mark -
429+
/**
430+
* The target of registration of plugins.
431+
*
432+
* This often is hooked up to the GeneratedPluginRegistrant which is
433+
* automatically generated by Flutter for the dependencies listed in the
434+
* project.
435+
*/
436+
@protocol FlutterPluginRegistrant <NSObject>
437+
@required
438+
/**
439+
* Register all the plugins for the registrant.
440+
*
441+
* This will be called after a FlutterEngine has been instantiated, the registry
442+
* will connect any plugins to that engine.
443+
*
444+
* @param registry The registry where plugins will be registered.
445+
*/
446+
- (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
447+
@end
448+
428449
#pragma mark -
429450
/**
430451
* Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register

engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ FLUTTER_DARWIN_EXPORT
254254
*/
255255
@property(nonatomic, readonly) BOOL engineAllowHeadlessExecution;
256256

257+
/**
258+
* The plugin registrant that will be executed when the FlutterViewController is
259+
* created with a NIB.
260+
*
261+
* This is only necessary when working with NIBs (XIBs and Storyboards). When
262+
* programatically creating FlutterViewControllers, plugins can be registered
263+
* directly.
264+
*/
265+
@property(nonatomic, weak) IBOutlet NSObject<FlutterPluginRegistrant>* pluginRegistrant;
266+
257267
@end
258268

259269
NS_ASSUME_NONNULL_END

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
static NSString* const kBackgroundFetchCapatibility = @"fetch";
2121
static NSString* const kRestorationStateAppModificationKey = @"mod-date";
2222

23-
@interface FlutterAppDelegate ()
23+
@interface FlutterAppDelegate () {
24+
__weak NSObject<FlutterPluginRegistrant>* _weakRegistrant;
25+
NSObject<FlutterPluginRegistrant>* _strongRegistrant;
26+
}
2427
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
2528
@property(nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
2629
@end
@@ -228,6 +231,26 @@ - (BOOL)application:(UIApplication*)application
228231

229232
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
230233

234+
- (NSObject<FlutterPluginRegistrant>*)pluginRegistrant {
235+
if (_weakRegistrant) {
236+
return _weakRegistrant;
237+
}
238+
if (_strongRegistrant) {
239+
return _strongRegistrant;
240+
}
241+
return nil;
242+
}
243+
244+
- (void)setPluginRegistrant:(NSObject<FlutterPluginRegistrant>*)pluginRegistrant {
245+
if (pluginRegistrant == (id)self) {
246+
_weakRegistrant = pluginRegistrant;
247+
_strongRegistrant = nil;
248+
} else {
249+
_weakRegistrant = nil;
250+
_strongRegistrant = pluginRegistrant;
251+
}
252+
}
253+
231254
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
232255
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
233256
if (flutterRootViewController) {

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,20 @@ - (void)testUseNonDeprecatedOpenURLAPI {
198198
completionHandler:[OCMArg any]]);
199199
}
200200

201+
- (void)testSetGetPluginRegistrant {
202+
id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant));
203+
self.appDelegate.pluginRegistrant = mockRegistrant;
204+
XCTAssertEqual(self.appDelegate.pluginRegistrant, mockRegistrant);
205+
}
206+
207+
- (void)testSetGetPluginRegistrantSelf {
208+
__weak FlutterAppDelegate* appDelegate = self.appDelegate;
209+
@autoreleasepool {
210+
appDelegate.pluginRegistrant = (id)appDelegate;
211+
self.appDelegate = nil;
212+
}
213+
// A retain cycle would keep this alive.
214+
XCTAssertNil(appDelegate);
215+
}
216+
201217
@end

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ - (void)awakeFromNib {
247247
if (!self.engine) {
248248
[self sharedSetupWithProject:nil initialRoute:nil];
249249
}
250+
if (self.pluginRegistrant) {
251+
[self.pluginRegistrant registerWithRegistry:self];
252+
}
250253
}
251254

252255
- (instancetype)init {
@@ -281,6 +284,13 @@ - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
281284
// Eliminate method calls in initializers and dealloc.
282285
[self loadDefaultSplashScreenView];
283286
[self performCommonViewControllerInitialization];
287+
288+
if ([FlutterSharedApplication.application.delegate
289+
respondsToSelector:@selector(pluginRegistrant)]) {
290+
NSObject<FlutterPluginRegistrant>* pluginRegistrant =
291+
[FlutterSharedApplication.application.delegate performSelector:@selector(pluginRegistrant)];
292+
[pluginRegistrant registerWithRegistry:self];
293+
}
284294
}
285295

286296
- (BOOL)isViewOpaque {

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
2323
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
2424
#import "flutter/shell/platform/embedder/embedder.h"
25+
#import "flutter/testing/ios/IosUnitTests/App/AppDelegate.h"
2526
#import "flutter/third_party/spring_animation/spring_animation.h"
2627

2728
FLUTTER_ASSERT_ARC
@@ -2471,4 +2472,22 @@ - (void)testStateIsActiveAndBackgroundWhenSceneStateIsInactive {
24712472
[mockVC stopMocking];
24722473
}
24732474

2475+
- (void)testPluginRegistrant {
2476+
id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant));
2477+
FlutterViewController* viewController = [[FlutterViewController alloc] init];
2478+
viewController.pluginRegistrant = mockRegistrant;
2479+
[viewController awakeFromNib];
2480+
OCMVerify([mockRegistrant registerWithRegistry:viewController]);
2481+
}
2482+
2483+
- (void)testAppDelegatePluginRegistrant {
2484+
id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant));
2485+
id appDelegate = [[UIApplication sharedApplication] delegate];
2486+
XCTAssertTrue([appDelegate respondsToSelector:@selector(setPluginRegistrant:)]);
2487+
[appDelegate setPluginRegistrant:mockRegistrant];
2488+
FlutterViewController* viewController = [[FlutterViewController alloc] init];
2489+
[appDelegate setPluginRegistrant:nil];
2490+
OCMVerify([mockRegistrant registerWithRegistry:viewController]);
2491+
}
2492+
24742493
@end

engine/src/flutter/testing/ios/IosUnitTests/App/AppDelegate.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77

88
#import <UIKit/UIKit.h>
99

10+
@protocol FlutterPluginRegistrant;
11+
1012
@interface AppDelegate : UIResponder <UIApplicationDelegate>
1113

12-
@property(strong, nonatomic) UIWindow* window;
14+
@property(nonatomic, strong, nullable) UIWindow* window;
15+
16+
// A mirror of the FlutterAppDelegate API for integration testing.
17+
@property(nonatomic, strong, nullable) NSObject<FlutterPluginRegistrant>* pluginRegistrant;
1318

1419
@end
1520

0 commit comments

Comments
 (0)