diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index 864dfffb526e3..7d944785c4f45 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -28,6 +28,7 @@ public final class FlutterInjector { *
This can only be called at the beginning of the program before the {@link #instance()} is * accessed. */ + @VisibleForTesting public static void setInstance(@NonNull FlutterInjector injector) { if (accessed) { throw new IllegalStateException( diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 7446522a5b6c7..22993f08fe58e 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -44,7 +44,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.view.FlutterMain; /** * {@code Activity} which displays a fullscreen Flutter UI. @@ -754,12 +753,14 @@ public String getInitialRoute() { } /** - * The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots. + * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code + * snapshots. * *
When this {@code FlutterActivity} is run by Flutter tooling and a data String is included in * the launching {@code Intent}, that data String is interpreted as an app bundle path. * - *
By default, the app bundle path is obtained from {@link FlutterMain#findAppBundlePath()}. + *
When otherwise unspecified, the value is null, which defaults to the app bundle path defined + * in {@link FlutterLoader#findAppBundlePath()}. * *
Subclasses may override this method to return a custom app bundle path. */ @@ -776,9 +777,7 @@ public String getAppBundlePath() { } } - // Return the default app bundle path. - // TODO(mattcarroll): move app bundle resolution into an appropriately named class. - return FlutterMain.findAppBundlePath(); + return null; } /** diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index f42b649135cb2..e4cad301c21a0 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -18,6 +18,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.app.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; @@ -366,10 +367,15 @@ private void doInitialFlutterViewRun() { flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute()); } + String appBundlePathOverride = host.getAppBundlePath(); + if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) { + appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath(); + } + // Configure the Dart entrypoint and execute it. DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint( - host.getAppBundlePath(), host.getDartEntrypointFunctionName()); + appBundlePathOverride, host.getDartEntrypointFunctionName()); flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index a0d1120360afe..d64b6788e71a6 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -22,7 +22,6 @@ import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.view.FlutterMain; /** * {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space. @@ -226,8 +225,8 @@ public NewEngineFragmentBuilder initialRoute(@NonNull String initialRoute) { } /** - * The path to the app bundle which contains the Dart app to execute, defaults to {@link - * FlutterMain#findAppBundlePath()} + * The path to the app bundle which contains the Dart app to execute. Null when unspecified, + * which defaults to {@link FlutterLoader#findAppBundlePath()} */ @NonNull public NewEngineFragmentBuilder appBundlePath(@NonNull String appBundlePath) { @@ -800,16 +799,18 @@ public String getDartEntrypointFunctionName() { } /** - * Returns the file path to the desired Flutter app's bundle of code. + * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code + * snapshots. * - *
Defaults to {@link FlutterMain#findAppBundlePath()}. + *
When unspecified, the value is null, which defaults to the app bundle path defined in {@link + * FlutterLoader#findAppBundlePath()}. * *
Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host} */ @Override @NonNull public String getAppBundlePath() { - return getArguments().getString(ARG_APP_BUNDLE_PATH, FlutterMain.findAppBundlePath()); + return getArguments().getString(ARG_APP_BUNDLE_PATH); } /** diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 2a1cb94241fb1..ff15581a57b22 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -41,7 +41,6 @@ import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.view.FlutterMain; /** * A Flutter {@code Activity} that is based upon {@link FragmentActivity}. @@ -581,14 +580,15 @@ public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) { } /** - * The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots. + * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code + * snapshots. * *
When this {@code FlutterFragmentActivity} is run by Flutter tooling and a data String is * included in the launching {@code Intent}, that data String is interpreted as an app bundle * path. * - *
By default, the app bundle path is obtained from {@link - * FlutterMain#findAppBundlePath(Context)}. + *
When otherwise unspecified, the value is null, which defaults to the app bundle path defined + * in {@link FlutterLoader#findAppBundlePath()}. * *
Subclasses may override this method to return a custom app bundle path. */ @@ -605,9 +605,7 @@ protected String getAppBundlePath() { } } - // Return the default app bundle path. - // TODO(mattcarroll): move app bundle resolution into an appropriately named class. - return FlutterMain.findAppBundlePath(); + return null; } /** diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 8967656fa870e..7ef0b048e7e8f 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -17,11 +17,13 @@ import android.content.Intent; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; +import io.flutter.FlutterInjector; import io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.Host; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; @@ -51,6 +53,7 @@ public class FlutterActivityAndFragmentDelegateTest { @Before public void setup() { + FlutterInjector.reset(); // Create a mocked FlutterEngine for the various interactions required by the delegate // being tested. mockFlutterEngine = mockFlutterEngine(); @@ -307,6 +310,35 @@ public void itExecutesDartEntrypointProvidedByHost() { verify(mockFlutterEngine.getDartExecutor(), times(1)).executeDartEntrypoint(eq(dartEntrypoint)); } + @Test + public void itUsesDefaultFlutterLoaderAppBundlePathWhenUnspecified() { + // ---- Test setup ---- + FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); + when(mockFlutterLoader.findAppBundlePath()).thenReturn("default_flutter_assets/path"); + FlutterInjector.setInstance( + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + + // Set Dart entrypoint parameters on fake host. + when(mockHost.getAppBundlePath()).thenReturn(null); + when(mockHost.getDartEntrypointFunctionName()).thenReturn("myEntrypoint"); + + // Create the DartEntrypoint that we expect to be executed. + DartExecutor.DartEntrypoint dartEntrypoint = + new DartExecutor.DartEntrypoint("default_flutter_assets/path", "myEntrypoint"); + + // Create the real object that we're testing. + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + + // --- Execute the behavior under test --- + // Dart is executed in onStart(). + delegate.onAttach(RuntimeEnvironment.application); + delegate.onCreateView(null, null, null); + delegate.onStart(); + + // Verify that the host's Dart entrypoint was used. + verify(mockFlutterEngine.getDartExecutor(), times(1)).executeDartEntrypoint(eq(dartEntrypoint)); + } + // "Attaching" to the surrounding Activity refers to Flutter being able to control // system chrome and other Activity-level details. If Flutter is not attached to the // surrounding Activity, it cannot control those details. This includes plugins.