Skip to content

Commit

Permalink
Add a java injector for testing (#20789)
Browse files Browse the repository at this point in the history
  • Loading branch information
xster committed Aug 30, 2020
1 parent cb9439a commit 15f5696
Show file tree
Hide file tree
Showing 18 changed files with 393 additions and 111 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/android/BUILD.gn
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
136 changes: 136 additions & 0 deletions 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.
*
* <p>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}.
*
* <p>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.
*
* <p>Once you access it, you can no longer change the values injected.
*
* <p>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.
*
* <p>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)}.
*
* <p>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.
*
* <p>Useful for testing since JVM tests via Robolectric can't load native libraries.
*
* <p>Defaults to true.
*/
public Builder setShouldLoadNative(boolean shouldLoadNative) {
this.shouldLoadNative = shouldLoadNative;
return this;
}

private FlutterLoader flutterLoader;
/**
* Sets a {@link FlutterLoader} override.
*
* <p>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);
}
}
}
Expand Up @@ -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.
*
* <p>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}.
Expand All @@ -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.
*
* <p>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 {
Expand Down
4 changes: 2 additions & 2 deletions shell/platform/android/io/flutter/app/FlutterApplication.java
Expand Up @@ -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
Expand All @@ -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;
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -131,7 +132,8 @@ public void onPreEngineRestart() {
* <p>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);
Expand All @@ -143,7 +145,7 @@ public FlutterEngine(@NonNull Context context) {
* <p>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);
}

/**
Expand All @@ -158,7 +160,7 @@ public FlutterEngine(
boolean automaticallyRegisterPlugins) {
this(
context,
FlutterLoader.getInstance(),
/* flutterLoader */ null,
new FlutterJNI(),
dartVmArgs,
automaticallyRegisterPlugins);
Expand Down Expand Up @@ -189,7 +191,7 @@ public FlutterEngine(
boolean waitForRestorationData) {
this(
context,
FlutterLoader.getInstance(),
/* flutterLoader */ null,
new FlutterJNI(),
new PlatformViewsController(),
dartVmArgs,
Expand All @@ -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);
}
Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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);

Expand Down
Expand Up @@ -15,8 +15,8 @@
* <p>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 {
Expand Down
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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. */
Expand Down

0 comments on commit 15f5696

Please sign in to comment.