Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
53 changes: 45 additions & 8 deletions shell/platform/darwin/ios/framework/Headers/FlutterEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

NS_ASSUME_NONNULL_BEGIN

/**
* The dart entrypoint that is associated with `main()`. This is to be used as an argument to the
* `runWithEntrypoint*` methods.
*/
extern NSString* const FlutterDefaultDartEntrypoint;

/**
* The FlutterEngine class coordinates a single instance of execution for a
* `FlutterDartProject`. It may have zero or one `FlutterViewController` at a
Expand All @@ -41,6 +47,26 @@ NS_ASSUME_NONNULL_BEGIN
*/
FLUTTER_EXPORT
@interface FlutterEngine : NSObject <FlutterTextureRegistry, FlutterPluginRegistry>

/**
* Initialize this FlutterEngine.
*
* The engine will execute the project located in the bundle with the identifier
* "io.flutter.flutter.app" (the default for Flutter projects).
*
* A newly initialized engine will not run until either `-runWithEntrypoint:` or
* `-runWithEntrypoint:libraryURI:` is called.
*
* FlutterEngine created with this method will have allowHeadlessExecution set to `YES`.
* This means that the engine will continue to run regardless of whether a `FlutterViewController`
* is attached to it or not, until `-destroyContext:` is called or the process finishes.
*
* @param labelPrefix The label prefix used to identify threads for this instance. Should
* be unique across FlutterEngine instances, and is used in instrumentation to label
* the threads used by this FlutterEngine.
*/
- (instancetype)initWithName:(NSString*)labelPrefix;

/**
* Initialize this FlutterEngine with a `FlutterDartProject`.
*
Expand Down Expand Up @@ -91,6 +117,17 @@ FLUTTER_EXPORT

+ (instancetype)new NS_UNAVAILABLE;

/**
* Runs a Dart program on an Isolate from the main Dart library (i.e. the library that
* contains `main()`), using `main()` as the entrypoint (the default for Flutter projects).
*
* The first call to this method will create a new Isolate. Subsequent calls will return
* immediately.
*
* @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise.
*/
- (BOOL)run;

/**
* Runs a Dart program on an Isolate from the main Dart library (i.e. the library that
* contains `main()`).
Expand All @@ -99,10 +136,10 @@ FLUTTER_EXPORT
* immediately.
*
* @param entrypoint The name of a top-level function from the same Dart
* library that contains the app's main() function. If this is nil, it will
* default to `main()`. If it is not the app's main() function, that function
* must be decorated with `@pragma(vm:entry-point)` to ensure the method is not
* tree-shaken by the Dart compiler.
* library that contains the app's main() function. If this is FlutterDefaultDartEntrypoint (or
* nil) it will default to `main()`. If it is not the app's main() function, that function must
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: is it main()or main? The Android embedding seems to use just main

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that the literal value on Android is just the name of the function, no (). For the purpose of documentation it might be useful to represent it as "main()" for clarity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want to allow nil here? I explicitly didn't allow that on the Android side. Instead I offered a factory method for DartEntrypoint.createDefault() to make it easy to resolve, but still explicitly selecting the entrypoint.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the interface and I don't like the use of nil as placeholders for future calculations either. In this PR I've tried to make it so no one ever has to mention a nil to create or run an Engine. Truly eliminating the use of nil will require some excavation, a breaking change and some philosophy debates. I've avoided that to make some concrete progress on the issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

* be decorated with `@pragma(vm:entry-point)` to ensure the method is not tree-shaken by the Dart
* compiler.
* @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise.
*/
- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with how things are packaged at the binary level in iOS, but do we not need a configurable bundle path along with the function name? The entrypoint on Android has both pieces in a data structure called DartEntrypoint...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- (instancetype)initWithName:(NSString*)labelPrefix project:(nullable FlutterDartProject*)project;
. You specify the bundle path when you create the FlutterEngine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that's separated? Is the goal of this change to match Android? If so, shouldn't that involve pairing the entrypoint with the bundle path? The entrypoint only exists in a specific bundle, so if we think it's legitimate to lazily choose the entrypoint method, then it seems like you should be able to lazily choose the bundle path, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a legitimate point. What you are proposing is a breaking change though. The idea with this PR is it gets us closer to Android without breaking. We can see what @xster thinks. I'd say that could be a different PR though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can see what @xster says, but seems like we could introduce a new constructor and a new dart entrypoint setter method, then soft deprecate the existing ones. That wouldn't be a breaking change, but it would move us forward for the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@xster xster Sep 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya, we're in kind of a pickle. The Android engine constructor doesn't ask for anything and everything's provided by the dart executor's execute.

The iOS engine asks for half of the stuff during init and the other half during run.

I think the ideal solution is probably:

  • Deprecate the initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project init. If you give it a project, we'll just ignore it.
  • Let the launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil also have a withProject:(FlutterDartProject*)project to fulfill this API.
  • Let there also be a launchDefaultEngine or some such so you can just [[[FlutterEngine alloc] initWithName:@"blah"] launchDefaultEngine] so that the most default engine can be launched without params or nils like Android.

It looks like _dartProject is only ever used in the launchEngine method in the FlutterEngine so moving it implementation wise shouldn't alter too much.

The break would be ok (but still needs announcing) since the only valid use of that API is add-to-app currently and we never shipped add-to-app yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'll look into making this a bit deeper. If you want the API's to be closer though, shouldn't we introduce a class called FlutterDartEntrypoint in objc and make a method on FlutterEngine runWithDartEntrypoint:?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on how ambitious you want to get. If you want to go big, you can do flutter/flutter#33099 at the same time :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unable to add the bundle to the run arguments. This would negatively affect our app launch time for full flutter apps since the engine is created and launch is only called after viewWillAppear:

There are still a few things I can do to make working with the FlutterEngine nicer though. I'll do everything but move the bundle to run/launch arguments.

Expand All @@ -114,10 +151,10 @@ FLUTTER_EXPORT
* The first call to this method will create a new Isolate. Subsequent calls will return
* immediately.
*
* @param entrypoint The name of a top-level function from a Dart library. If nil, this will
* default to `main()`. If it is not the app's main() function, that function
* must be decorated with `@pragma(vm:entry-point)` to ensure the method is not
* tree-shaken by the Dart compiler.
* @param entrypoint The name of a top-level function from a Dart library. If this is
* FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's
* main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the
* method is not tree-shaken by the Dart compiler.
* @param uri The URI of the Dart library which contains the entrypoint method. IF nil,
* this will default to the same library as the `main()` function in the Dart program.
* @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise.
Expand Down
10 changes: 10 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"

NSString* const FlutterDefaultDartEntrypoint = nil;

@interface FlutterEngine () <FlutterTextInputDelegate, FlutterBinaryMessenger>
// Maintains a dictionary of plugin names that have registered with the engine. Used by
// FlutterEngineRegistrar to implement a FlutterPluginRegistrar.
Expand Down Expand Up @@ -70,6 +72,10 @@ @implementation FlutterEngine {
FlutterBinaryMessengerRelay* _binaryMessenger;
}

- (instancetype)initWithName:(NSString*)labelPrefix {
return [self initWithName:labelPrefix project:nil allowHeadlessExecution:YES];
}

- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
return [self initWithName:labelPrefix project:project allowHeadlessExecution:YES];
}
Expand Down Expand Up @@ -429,6 +435,10 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
return _shell != nullptr;
}

- (BOOL)run {
return [self runWithEntrypoint:FlutterDefaultDartEntrypoint libraryURI:nil];
}

- (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
if ([self createShell:entrypoint libraryURI:libraryURI]) {
[self launchEngine:entrypoint libraryURI:libraryURI];
Expand Down