Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Implemented library uri support for FlutterFragments and FlutterActivities #30726

Merged
merged 3 commits into from
Jan 10, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.flutter.embedding.android;

import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_URI_META_DATA_KEY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
Expand Down Expand Up @@ -824,6 +825,32 @@ public String getDartEntrypointFunctionName() {
}
}

/**
* The Dart library URI for the entrypoint that will be executed as soon as the Dart snapshot is
* loaded.
*
* <p>Example value: "package:foo/bar.dart"
*
* <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
Copy link

Choose a reason for hiding this comment

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

btw, the manifest can have dynamic values that are passed from the tool to Gradle, then copied into the final manifest. e.g. https://github.com/flutter/flutter/blob/master/packages/flutter_tools/templates/app_shared/android.tmpl/app/src/main/AndroidManifest.xml.tmpl#L5

cc @GaryQian

Copy link

Choose a reason for hiding this comment

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

This needs another migration of existing manifest files

* FlutterActivityLaunchConfigs#DART_ENTRYPOINT_URI_META_DATA_KEY} within the Android manifest
* definition for this {@code FlutterActivity}.
*
* <p>A value of null means use the default root library.
*
* <p>Subclasses may override this method to directly control the Dart entrypoint uri.
*/
@Nullable
public String getDartEntrypointLibraryUri() {
try {
Bundle metaData = getMetaData();
String desiredDartLibraryUri =
metaData != null ? metaData.getString(DART_ENTRYPOINT_URI_META_DATA_KEY) : null;
return desiredDartLibraryUri;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}

/**
* The initial route that a Flutter app will render upon loading and executing its Dart code.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,16 @@ private void doInitialFlutterViewRun() {
initialRoute = DEFAULT_INITIAL_ROUTE;
}
}
@Nullable String libraryUri = host.getDartEntrypointLibraryUri();
Log.v(
TAG,
"Executing Dart entrypoint: "
+ host.getDartEntrypointFunctionName()
+ ", and sending initial route: "
+ initialRoute);
+ host.getDartEntrypointFunctionName()
+ ", library uri: "
+ libraryUri
== null
? "\"\""
: libraryUri + ", and sending initial route: " + initialRoute);

// The engine needs to receive the Flutter app's initial route before executing any
// Dart code to ensure that the initial route arrives in time to be applied.
Expand All @@ -435,8 +439,11 @@ private void doInitialFlutterViewRun() {

// Configure the Dart entrypoint and execute it.
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
libraryUri == null
? new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName())
: new DartExecutor.DartEntrypoint(
appBundlePathOverride, libraryUri, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}

Expand Down Expand Up @@ -916,6 +923,14 @@ private void ensureAlive() {
@NonNull
String getDartEntrypointFunctionName();

/**
* Returns the URI of the Dart library which contains the entrypoint method (example
* "package:foo_package/main.dart"). If null, this will default to the same library as the
* `main()` function in the Dart program.
*/
@Nullable
String getDartEntrypointLibraryUri();

/** Returns the path to the app bundle where the Dart code exists. */
@NonNull
String getAppBundlePath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class FlutterActivityLaunchConfigs {
// Meta-data arguments, processed from manifest XML.
/* package */ static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint";
/* package */ static final String DART_ENTRYPOINT_URI_META_DATA_KEY = "io.flutter.EntrypointUri";
/* package */ static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute";
/* package */ static final String SPLASH_SCREEN_META_DATA_KEY =
"io.flutter.embedding.android.SplashScreenDrawable";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public class FlutterFragment extends Fragment

/** The Dart entrypoint method name that is executed upon initialization. */
protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
/** The Dart entrypoint method's URI that is executed upon initialization. */
protected static final String ARG_DART_ENTRYPOINT_URI = "dart_entrypoint_uri";
/** Initial Flutter route that is rendered in a Navigator widget. */
protected static final String ARG_INITIAL_ROUTE = "initial_route";
/** Whether the activity delegate should handle the deeplinking request. */
Expand Down Expand Up @@ -223,6 +225,7 @@ public static NewEngineFragmentBuilder withNewEngine() {
public static class NewEngineFragmentBuilder {
private final Class<? extends FlutterFragment> fragmentClass;
private String dartEntrypoint = "main";
private String dartLibraryUri = null;
private String initialRoute = "/";
private boolean handleDeeplinking = false;
private String appBundlePath = null;
Expand Down Expand Up @@ -256,6 +259,12 @@ public NewEngineFragmentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
return this;
}

@NonNull
public NewEngineFragmentBuilder dartLibraryUri(@NonNull String dartLibraryUri) {
this.dartLibraryUri = dartLibraryUri;
return this;
}

/**
* The initial route that a Flutter app will render in this {@link FlutterFragment}, defaults to
* "/".
Expand Down Expand Up @@ -410,6 +419,7 @@ protected Bundle createArgs() {
args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking);
args.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);
args.putString(ARG_DART_ENTRYPOINT_URI, dartLibraryUri);
// TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of
// conflating.
if (null != shellArgs) {
Expand Down Expand Up @@ -1026,6 +1036,20 @@ public String getDartEntrypointFunctionName() {
return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
}

/**
* Returns the library URI of the Dart method that this {@code FlutterFragment} should execute to
* start a Flutter app.
*
* <p>Defaults to null (example value: "package:foo/bar.dart").
*
* <p>Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
@Nullable
public String getDartEntrypointLibraryUri() {
return getArguments().getString(ARG_DART_ENTRYPOINT_URI);
}

/**
* A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code
* snapshots.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,25 @@ public void itExecutesDartEntrypointProvidedByHost() {
verify(mockFlutterEngine.getDartExecutor(), times(1)).executeDartEntrypoint(eq(dartEntrypoint));
}

@Test
public void itExecutesDartLibraryUriProvidedByHost() {
when(mockHost.getAppBundlePath()).thenReturn("/my/bundle/path");
when(mockHost.getDartEntrypointFunctionName()).thenReturn("myEntrypoint");
when(mockHost.getDartEntrypointLibraryUri()).thenReturn("package:foo/bar.dart");

DartExecutor.DartEntrypoint expectedEntrypoint =
new DartExecutor.DartEntrypoint("/my/bundle/path", "package:foo/bar.dart", "myEntrypoint");

FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

delegate.onAttach(RuntimeEnvironment.application);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();

verify(mockFlutterEngine.getDartExecutor(), times(1))
.executeDartEntrypoint(eq(expectedEntrypoint));
}

@Test
public void itUsesDefaultFlutterLoaderAppBundlePathWhenUnspecified() {
// ---- Test setup ----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public void itCreatesDefaultIntentWithExpectedDefaults() {
flutterActivity.setDelegate(new FlutterActivityAndFragmentDelegate(flutterActivity));

assertEquals("main", flutterActivity.getDartEntrypointFunctionName());
assertNull(flutterActivity.getDartEntrypointLibraryUri());
assertEquals("/", flutterActivity.getInitialRoute());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,12 @@ public String getDartEntrypointFunctionName() {
return "main";
}

@Nullable
@Override
public String getDartEntrypointLibraryUri() {
return null;
}

@NonNull
@Override
public String getAppBundlePath() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void itCreatesDefaultFragmentWithExpectedDefaults() {
fragment.setDelegate(new FlutterActivityAndFragmentDelegate(fragment));

assertEquals("main", fragment.getDartEntrypointFunctionName());
assertNull(fragment.getDartEntrypointLibraryUri());
assertEquals("/", fragment.getInitialRoute());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
assertTrue(fragment.shouldAttachEngineToActivity());
Expand All @@ -54,6 +55,7 @@ public void itCreatesNewEngineFragmentWithRequestedSettings() {
FlutterFragment fragment =
FlutterFragment.withNewEngine()
.dartEntrypoint("custom_entrypoint")
.dartLibraryUri("package:foo/bar.dart")
.initialRoute("/custom/route")
.shouldAttachEngineToActivity(false)
.handleDeeplinking(true)
Expand All @@ -63,6 +65,7 @@ public void itCreatesNewEngineFragmentWithRequestedSettings() {
fragment.setDelegate(new FlutterActivityAndFragmentDelegate(fragment));

assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
assertEquals("package:foo/bar.dart", fragment.getDartEntrypointLibraryUri());
assertEquals("/custom/route", fragment.getInitialRoute());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
assertFalse(fragment.shouldAttachEngineToActivity());
Expand Down
15 changes: 9 additions & 6 deletions shell/platform/darwin/ios/framework/Headers/FlutterEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,9 @@ FLUTTER_DARWIN_EXPORT
* 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.
* @param uri The URI of the Dart library which contains the entrypoint method
* (example "package:foo_package/main.dart"). 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.
*/
- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)uri;
Expand All @@ -236,8 +237,9 @@ FLUTTER_DARWIN_EXPORT
* 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 libraryURI 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.
* @param libraryURI The URI of the Dart library which contains the entrypoint
* method (example "package:foo_package/main.dart"). If nil, this will
* default to the same library as the `main()` function in the Dart program.
* @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
* FlutterDefaultInitialRoute (or nil), it will default to the "/" route.
* @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise.
Expand All @@ -257,8 +259,9 @@ FLUTTER_DARWIN_EXPORT
* 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 libraryURI 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.
* @param libraryURI The URI of the Dart library which contains the entrypoint
* method (example "package:foo_package/main.dart"). If nil, this will
* default to the same library as the `main()` function in the Dart program.
* @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
* FlutterDefaultInitialRoute (or nil), it will default to the "/" route.
* @param entrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
Expand Down