diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 09bc2ba6555c4..611cdc4dba0bf 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -694,6 +694,7 @@ FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_poo FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc FILE: ../../../flutter/shell/platform/android/flutter_main.cc FILE: ../../../flutter/shell/platform/android/flutter_main.h +FILE: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 3fe51be12a214..23300cf01a4d3 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -117,6 +117,7 @@ embedding_sources_jar_filename = "$embedding_artifact_id-sources.jar" embedding_source_jar_path = "$root_out_dir/$embedding_sources_jar_filename" android_java_sources = [ + "io/flutter/FlutterInjector.java", "io/flutter/Log.java", "io/flutter/app/FlutterActivity.java", "io/flutter/app/FlutterActivityDelegate.java", @@ -415,6 +416,7 @@ action("robolectric_tests") { jar_path = "$root_out_dir/robolectric_tests.jar" sources = [ + "test/io/flutter/FlutterInjectorTest.java", "test/io/flutter/FlutterTestSuite.java", "test/io/flutter/SmokeTest.java", "test/io/flutter/embedding/android/AndroidKeyProcessorTest.java", @@ -434,6 +436,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/RenderingComponentTest.java", "test/io/flutter/embedding/engine/dart/DartExecutorTest.java", "test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java", + "test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java", "test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java", "test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java", "test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java", diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java new file mode 100644 index 0000000000000..864dfffb526e3 --- /dev/null +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -0,0 +1,136 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter; + +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import io.flutter.embedding.engine.loader.FlutterLoader; + +/** + * This class is a simple dependency injector for the relatively thin Android part of the Flutter + * engine. + * + *
This simple solution is used facilitate testability without bringing in heavier + * app-development centric dependency injection frameworks such as Guice or Dagger2 or spreading + * construction injection everywhere. + */ +public final class FlutterInjector { + + private static FlutterInjector instance; + private static boolean accessed; + + /** + * Use {@link FlutterInjector.Builder} to specify members to be injected via the static {@code + * FlutterInjector}. + * + *
This can only be called at the beginning of the program before the {@link #instance()} is + * accessed. + */ + public static void setInstance(@NonNull FlutterInjector injector) { + if (accessed) { + throw new IllegalStateException( + "Cannot change the FlutterInjector instance once it's been " + + "read. If you're trying to dependency inject, be sure to do so at the beginning of " + + "the program"); + } + instance = injector; + } + + /** + * Retrieve the static instance of the {@code FlutterInjector} to use in your program. + * + *
Once you access it, you can no longer change the values injected. + * + *
If no override is provided for the injector, reasonable defaults are provided. + */ + public static FlutterInjector instance() { + accessed = true; + if (instance == null) { + instance = new Builder().build(); + } + return instance; + } + + // This whole class is here to enable testing so to test the thing that lets you test, some degree + // of hack is needed. + @VisibleForTesting + public static void reset() { + accessed = false; + instance = null; + } + + private FlutterInjector(boolean shouldLoadNative, @NonNull FlutterLoader flutterLoader) { + this.shouldLoadNative = shouldLoadNative; + this.flutterLoader = flutterLoader; + } + + private boolean shouldLoadNative; + private FlutterLoader flutterLoader; + + /** + * Returns whether the Flutter Android engine embedding should load the native C++ engine. + * + *
Useful for testing since JVM tests via Robolectric can't load native libraries. + */ + public boolean shouldLoadNative() { + return shouldLoadNative; + } + + /** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */ + @NonNull + public FlutterLoader flutterLoader() { + return flutterLoader; + } + + /** + * Builder used to supply a custom FlutterInjector instance to {@link + * FlutterInjector#setInstance(FlutterInjector)}. + * + *
Non-overriden values have reasonable defaults. + */ + public static final class Builder { + + private boolean shouldLoadNative = true; + /** + * Sets whether the Flutter Android engine embedding should load the native C++ engine. + * + *
Useful for testing since JVM tests via Robolectric can't load native libraries. + * + *
Defaults to true. + */ + public Builder setShouldLoadNative(boolean shouldLoadNative) { + this.shouldLoadNative = shouldLoadNative; + return this; + } + + private FlutterLoader flutterLoader; + /** + * Sets a {@link FlutterLoader} override. + * + *
A reasonable default will be used if unspecified. + */ + public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) { + this.flutterLoader = flutterLoader; + return this; + } + + private void fillDefaults() { + if (flutterLoader == null) { + flutterLoader = new FlutterLoader(); + } + } + + /** + * Builds a {@link FlutterInjector} from the builder. Unspecified properties will have + * reasonable defaults. + */ + public FlutterInjector build() { + fillDefaults(); + + System.out.println("should load native is " + shouldLoadNative); + return new FlutterInjector(shouldLoadNative, flutterLoader); + } + } +} diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java index b1619baebf82b..ee96cd072443b 100644 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java @@ -35,7 +35,8 @@ import java.util.ArrayList; /** - * Class that performs the actual work of tying Android {@link Activity} instances to Flutter. + * Deprecated class that performs the actual work of tying Android {@link Activity} instances to + * Flutter. * *
This exists as a dedicated class (as opposed to being integrated directly into {@link * FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}. @@ -48,6 +49,10 @@ * FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make * your activity implement {@link PluginRegistry} and/or {@link * io.flutter.view.FlutterView.Provider} and forward those methods to this class as well. + * + *
Deprecation: {@link io.flutter.embedding.android.FlutterActivity} is the new API that now + * replaces this class and {@link io.flutter.app.FlutterActivity}. See + * https://flutter.dev/go/android-project-migration for more migration details. */ public final class FlutterActivityDelegate implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry { diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java index cd55217176ef7..a211c268548cd 100644 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ b/shell/platform/android/io/flutter/app/FlutterApplication.java @@ -7,7 +7,7 @@ import android.app.Activity; import android.app.Application; import androidx.annotation.CallSuper; -import io.flutter.view.FlutterMain; +import io.flutter.FlutterInjector; /** * Flutter implementation of {@link android.app.Application}, managing application-level global @@ -21,7 +21,7 @@ public class FlutterApplication extends Application { @CallSuper public void onCreate() { super.onCreate(); - FlutterMain.startInitialization(this); + FlutterInjector.instance().flutterLoader().startInitialization(this); } private Activity mCurrentActivity = null; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 1408156ebaf24..9ec50d7ef98eb 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -7,6 +7,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; @@ -131,7 +132,8 @@ public void onPreEngineRestart() { *
In order to pass Dart VM initialization arguments (see {@link * io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the * initialization arguments by calling {@link FlutterLoader#startInitialization(Context)} and - * {@link FlutterLoader#ensureInitializationComplete(Context, String[])}. + * {@link FlutterLoader#ensureInitializationComplete(Context, String[])} before constructing the + * engine. */ public FlutterEngine(@NonNull Context context) { this(context, null); @@ -143,7 +145,7 @@ public FlutterEngine(@NonNull Context context) { *
If the Dart VM has already started, the given arguments will have no effect. */ public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) { - this(context, FlutterLoader.getInstance(), new FlutterJNI(), dartVmArgs, true); + this(context, /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, true); } /** @@ -158,7 +160,7 @@ public FlutterEngine( boolean automaticallyRegisterPlugins) { this( context, - FlutterLoader.getInstance(), + /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, automaticallyRegisterPlugins); @@ -189,7 +191,7 @@ public FlutterEngine( boolean waitForRestorationData) { this( context, - FlutterLoader.getInstance(), + /* flutterLoader */ null, new FlutterJNI(), new PlatformViewsController(), dartVmArgs, @@ -206,7 +208,7 @@ public FlutterEngine( */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI) { this(context, flutterLoader, flutterJNI, null, true); } @@ -219,7 +221,7 @@ public FlutterEngine( */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins) { @@ -238,7 +240,7 @@ public FlutterEngine( */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, @@ -256,7 +258,7 @@ public FlutterEngine( /** Fully configurable {@code FlutterEngine} constructor. */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, @@ -280,6 +282,9 @@ public FlutterEngine( this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.flutterJNI = flutterJNI; + if (flutterLoader == null) { + flutterLoader = FlutterInjector.instance().flutterLoader(); + } flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java index 9f0c4eef07856..b064ef8f17eaf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -15,8 +15,8 @@ *
The term "shell" refers to the native code that adapts Flutter to different platforms. * Flutter's Android Java code initializes a native "shell" and passes these arguments to that * native shell when it is initialized. See {@link - * io.flutter.view.FlutterMain#ensureInitializationComplete(Context, String[])} for more - * information. + * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])} + * for more information. */ @SuppressWarnings({"WeakerAccess", "unused"}) public class FlutterShellArgs { diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 13b3ff05a62ea..3d6cb8416e21d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -8,12 +8,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StringCodec; import io.flutter.view.FlutterCallbackInformation; -import io.flutter.view.FlutterMain; import java.nio.ByteBuffer; /** @@ -250,9 +251,19 @@ public void notifyLowMemoryWarning() { * that entrypoint and other assets required for Dart execution. */ public static class DartEntrypoint { + /** + * Create a DartEntrypoint pointing to the default Flutter assets location with a default Dart + * entrypoint. + */ @NonNull public static DartEntrypoint createDefault() { - return new DartEntrypoint(FlutterMain.findAppBundlePath(), "main"); + FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader(); + + if (!flutterLoader.initialized()) { + throw new AssertionError( + "DartEntrypoints can only be created once a FlutterEngine is created."); + } + return new DartEntrypoint(flutterLoader.findAppBundlePath(), "main"); } /** The path within the AssetManager where the app will look for assets. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index 3df6fe097f81f..8af9539c94ea8 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.BuildConfig; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.util.PathUtils; import io.flutter.view.VsyncWaiter; @@ -47,7 +48,10 @@ public class FlutterLoader { *
The returned instance loads Flutter native libraries in the standard way. A singleton object * is used instead of static methods to facilitate testing without actually running native library * linking. + * + * @deprecated Use the {@link io.flutter.FlutterInjector} instead. */ + @Deprecated @NonNull public static FlutterLoader getInstance() { if (instance == null) { @@ -56,13 +60,6 @@ public static FlutterLoader getInstance() { return instance; } - @NonNull - public static FlutterLoader getInstanceForTest(FlutterApplicationInfo flutterApplicationInfo) { - FlutterLoader loader = new FlutterLoader(); - loader.flutterApplicationInfo = flutterApplicationInfo; - return loader; - } - private boolean initialized = false; @Nullable private Settings settings; private long initStartTimestampMillis; @@ -128,7 +125,9 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se public InitResult call() { ResourceExtractor resourceExtractor = initResources(appContext); - System.loadLibrary("flutter"); + if (FlutterInjector.instance().shouldLoadNative()) { + System.loadLibrary("flutter"); + } // Prefetch the default font manager as soon as possible on a background thread. // It helps to reduce time cost of engine setup that blocks the platform thread. @@ -231,13 +230,15 @@ public void ensureInitializationComplete( long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; - FlutterJNI.nativeInit( - applicationContext, - shellArgs.toArray(new String[0]), - kernelPath, - result.appStoragePath, - result.engineCachesPath, - initTimeMillis); + if (FlutterInjector.instance().shouldLoadNative()) { + FlutterJNI.nativeInit( + applicationContext, + shellArgs.toArray(new String[0]), + kernelPath, + result.appStoragePath, + result.engineCachesPath, + initTimeMillis); + } initialized = true; } catch (Exception e) { @@ -293,6 +294,11 @@ public void run() { }); } + /** Returns whether the FlutterLoader has finished loading the native library. */ + public boolean initialized() { + return initialized; + } + /** Extract assets out of the APK that need to be cached as uncompressed files on disk. */ private ResourceExtractor initResources(@NonNull Context applicationContext) { ResourceExtractor resourceExtractor = null; diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java index 9ec464e4f5736..9c40d0a016f21 100644 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java @@ -7,6 +7,7 @@ import android.app.Activity; import android.content.Context; import androidx.annotation.NonNull; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; @@ -14,7 +15,6 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.view.FlutterMain; import io.flutter.view.FlutterView; import io.flutter.view.TextureRegistry; import java.util.HashSet; @@ -85,12 +85,12 @@ public FlutterView view() { @Override public String lookupKeyForAsset(String asset) { - return FlutterMain.getLookupKeyForAsset(asset); + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset); } @Override public String lookupKeyForAsset(String asset, String packageName) { - return FlutterMain.getLookupKeyForAsset(asset, packageName); + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName); } @Override diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index 8cc80ec8dc96f..1b13b45aee208 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -8,7 +8,7 @@ import android.os.Handler; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.loader.FlutterLoader; /** @@ -42,10 +42,7 @@ public void setLogTag(String tag) { * @param applicationContext The Android application context. */ public static void startInitialization(@NonNull Context applicationContext) { - if (isRunningInRobolectricTest) { - return; - } - FlutterLoader.getInstance().startInitialization(applicationContext); + FlutterInjector.instance().flutterLoader().startInitialization(applicationContext); } /** @@ -61,12 +58,9 @@ public static void startInitialization(@NonNull Context applicationContext) { */ public static void startInitialization( @NonNull Context applicationContext, @NonNull Settings settings) { - if (isRunningInRobolectricTest) { - return; - } FlutterLoader.Settings newSettings = new FlutterLoader.Settings(); newSettings.setLogTag(settings.getLogTag()); - FlutterLoader.getInstance().startInitialization(applicationContext, newSettings); + FlutterInjector.instance().flutterLoader().startInitialization(applicationContext, newSettings); } /** @@ -79,10 +73,9 @@ public static void startInitialization( */ public static void ensureInitializationComplete( @NonNull Context applicationContext, @Nullable String[] args) { - if (isRunningInRobolectricTest) { - return; - } - FlutterLoader.getInstance().ensureInitializationComplete(applicationContext, args); + FlutterInjector.instance() + .flutterLoader() + .ensureInitializationComplete(applicationContext, args); } /** @@ -94,22 +87,20 @@ public static void ensureInitializationCompleteAsync( @Nullable String[] args, @NonNull Handler callbackHandler, @NonNull Runnable callback) { - if (isRunningInRobolectricTest) { - return; - } - FlutterLoader.getInstance() + FlutterInjector.instance() + .flutterLoader() .ensureInitializationCompleteAsync(applicationContext, args, callbackHandler, callback); } @NonNull public static String findAppBundlePath() { - return FlutterLoader.getInstance().findAppBundlePath(); + return FlutterInjector.instance().flutterLoader().findAppBundlePath(); } @Deprecated @Nullable public static String findAppBundlePath(@NonNull Context applicationContext) { - return FlutterLoader.getInstance().findAppBundlePath(); + return FlutterInjector.instance().flutterLoader().findAppBundlePath(); } /** @@ -121,7 +112,7 @@ public static String findAppBundlePath(@NonNull Context applicationContext) { */ @NonNull public static String getLookupKeyForAsset(@NonNull String asset) { - return FlutterLoader.getInstance().getLookupKeyForAsset(asset); + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset); } /** @@ -135,23 +126,6 @@ public static String getLookupKeyForAsset(@NonNull String asset) { */ @NonNull public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) { - return FlutterLoader.getInstance().getLookupKeyForAsset(asset, packageName); - } - - private static boolean isRunningInRobolectricTest = false; - - /* - * Indicates whether we are currently running in a Robolectric Test. - * - *
Flutter cannot be initialized inside a Robolectric environment since it cannot load
- * native libraries.
- *
- * @deprecated Use the new embedding (io.flutter.embedding) instead which provides better
- * modularity for testing.
- */
- @Deprecated
- @VisibleForTesting
- public static void setIsRunningInRobolectricTest(boolean isRunningInRobolectricTest) {
- FlutterMain.isRunningInRobolectricTest = isRunningInRobolectricTest;
+ return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName);
}
}
diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java
new file mode 100644
index 0000000000000..225c24959e136
--- /dev/null
+++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java
@@ -0,0 +1,61 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import io.flutter.embedding.engine.loader.FlutterLoader;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@Config(manifest = Config.NONE)
+@RunWith(RobolectricTestRunner.class)
+public class FlutterInjectorTest {
+ @Mock FlutterLoader mockFlutterLoader;
+
+ @Before
+ public void setUp() {
+ // Since the intent is to have a convenient static class to use for production.
+ FlutterInjector.reset();
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void itHasSomeReasonableDefaults() {
+ // Implicitly builds when first accessed.
+ FlutterInjector injector = FlutterInjector.instance();
+ assertNotNull(injector.flutterLoader());
+ assertTrue(injector.shouldLoadNative());
+ }
+
+ @Test
+ public void canPartiallyOverride() {
+ FlutterInjector.setInstance(
+ new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
+ FlutterInjector injector = FlutterInjector.instance();
+ assertEquals(injector.flutterLoader(), mockFlutterLoader);
+ assertTrue(injector.shouldLoadNative());
+ }
+
+ @Test()
+ public void cannotBeChangedOnceRead() {
+ FlutterInjector.instance();
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ FlutterInjector.setInstance(
+ new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
+ });
+ }
+}
diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java
index 67611eac687b8..bb5c44c6568df 100644
--- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java
+++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java
@@ -16,6 +16,8 @@
import io.flutter.embedding.engine.FlutterJNITest;
import io.flutter.embedding.engine.LocalizationPluginTest;
import io.flutter.embedding.engine.RenderingComponentTest;
+import io.flutter.embedding.engine.loader.ApplicationInfoLoaderTest;
+import io.flutter.embedding.engine.loader.FlutterLoaderTest;
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest;
import io.flutter.embedding.engine.renderer.FlutterRendererTest;
import io.flutter.embedding.engine.systemchannels.KeyEventChannelTest;
@@ -44,6 +46,7 @@
@SuiteClasses({
AccessibilityBridgeTest.class,
AndroidKeyProcessorTest.class,
+ ApplicationInfoLoaderTest.class,
DartExecutorTest.class,
FlutterActivityAndFragmentDelegateTest.class,
FlutterActivityTest.class,
@@ -53,8 +56,11 @@
FlutterEngineTest.class,
FlutterFragmentActivityTest.class,
FlutterFragmentTest.class,
+ FlutterInjectorTest.class,
FlutterJNITest.class,
FlutterLaunchTests.class,
+ FlutterLoaderTest.class,
+ FlutterShellArgsTest.class,
FlutterRendererTest.class,
FlutterShellArgsTest.class,
FlutterViewTest.class,
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
index 9a15364373b7a..6f141fa1aa192 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
@@ -3,6 +3,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -10,6 +11,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.loader.FlutterLoader;
@@ -137,4 +139,18 @@ public void itUsesApplicationContext() {
verify(context, atLeast(1)).getApplicationContext();
}
+
+ @Test
+ public void itCanUseFlutterLoaderInjectionViaFlutterInjector() {
+ FlutterInjector.reset();
+ FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
+ FlutterInjector.setInstance(
+ new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
+ Context mockContext = mock(Context.class);
+
+ new FlutterEngine(mockContext, null, flutterJNI);
+
+ verify(mockFlutterLoader, times(1)).startInitialization(any());
+ verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any());
+ }
}
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java
index 01d012c38b83a..71f47890245bc 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java
@@ -1,20 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
package test.io.flutter.embedding.engine;
import static junit.framework.TestCase.assertEquals;
-import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import androidx.annotation.NonNull;
+import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
-import io.flutter.embedding.engine.loader.FlutterApplicationInfo;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -22,47 +24,19 @@
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class PluginComponentTest {
+ @Before
+ public void setUp() {
+ FlutterInjector.reset();
+ }
+
@Test
public void pluginsCanAccessFlutterAssetPaths() {
// Setup test.
+ FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build());
FlutterJNI flutterJNI = mock(FlutterJNI.class);
when(flutterJNI.isAttached()).thenReturn(true);
- FlutterApplicationInfo emptyInfo =
- new FlutterApplicationInfo(null, null, null, null, null, null, false, false);
-
- // FlutterLoader is the object to which the PluginRegistry defers for obtaining
- // the path to a Flutter asset. Ideally in this component test we would use a
- // real FlutterLoader and directly verify the relationship between FlutterAssets
- // and FlutterLoader. However, a real FlutterLoader cannot be used in a JVM test
- // because it would attempt to load native libraries. Therefore, we create a fake
- // FlutterLoader, but then we defer the corresponding asset lookup methods to the
- // real FlutterLoader singleton. This test ends up verifying that when FlutterAssets
- // is queried for an asset path, it returns the real expected path based on real
- // FlutterLoader behavior.
- FlutterLoader flutterLoader = mock(FlutterLoader.class);
- when(flutterLoader.getLookupKeyForAsset(any(String.class)))
- .thenAnswer(
- new Answer