-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Description
Problem
Due to changes in initialization order, accessing the FlutterViewController from the application(_:didFinishLaunchingWithOptions:) method no longer works and will result in a crash (after migrating to UIScene).
Flutter’s official documentation was to set up app-level method channels in application:didFinishLaunching:.
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.dev/battery"
binaryMessenger:controller.binaryMessenger];
To mitigate this, #169399 introduced a callback called registerWithRegistry that can be used in a iOS project's AppDelegate. This callback returns an object that conforms to FlutterPluginRegistry. The FlutterAppDelegate, FlutterEngine, and FlutterViewController all conform to this protocol.
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
self.pluginRegistrant = self;
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[GeneratedPluginRegistrant registerWithRegistry:registry];
}
However, to create a method channel you need a binaryMessenger.
Proposal
Part 1: Split FlutterPluginRegistrar into 3 protocols: FlutterBaseRegistrar, FlutterPluginRegistrar, FlutterApplicationRegistrar
Introduce a new base protocol, called FlutterBaseRegistrar, make FlutterPluginRegistrar inherit from it, and move most methods up to the base.
Plugin specific methods can stay in FlutterPluginRegistrar, such as:
- addApplicationDelegate:, as application authors can do this directly instead.
- lookupKeyForAsset:*, as it’s not clear that this has a use case in the app context. (It could easily be moved up later if there is a use case).
- publish:, which similarly does not seem likely to be useful in the app context.
Introduce a new sibling protocol to FlutterPluginRegistrar called FlutterApplicationRegistrar, which inherits from the same base. For now this would have no new methods, but by adding this intermediate layer we can trivially add app-specific methods later if needed, without breaking changes or new delegate methods.
Part 2: Introduce a new protocol, callback, and interface for exposing the pluginRegistry and applicationRegistrar
First a new protocol, name TBD, would be introduced that AppDelegates can conform to. It would require a callback method to be added:
@protocol FlutterEngineInterfaceProvider
- (void)connectWithEngineInterface:(FlutterEngineInterface*)engineInterface;
@end@interface FlutterEngineInterface : NSObject
@property(nonatomic, strong) NSObject<FlutterPluginRegistry>* pluginRegistry;
@property(nonatomic, strong) NSObject<FlutterApplicationRegistrar>* applicationRegistrar;
@endThe callback method would pass in a new class that exposes certain parts of the engine, such as the pluginRegistry and applicationRegistrar. Then when the FlutterEngine starts up, if the AppDelegate singleton conforms to the protocol, it calls the callback.
Then from a developer's AppDelegate, they can do the following:
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterEngineInterfaceProvider {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication
.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(
application,
didFinishLaunchingWithOptions: launchOptions
)
}
func connect(with engineInterface: FlutterEngineInterface) {
GeneratedPluginRegistrant.register(with: engineInterface.pluginRegistry)
let batteryChannel = FlutterMethodChannel(
name: "samples.flutter.dev/battery",
binaryMessenger: engineInterface.applicationRegistrar.messenger()
)
}
}