Skip to content

[UIScene] Create a new callback for creating app-level method channels and platform views #173357

@vashworth

Description

@vashworth

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;
@end

The 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()
    )
  }
}

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions