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

Commit 15f5696

Browse files
authored
Add a java injector for testing (#20789)
1 parent cb9439a commit 15f5696

18 files changed

+393
-111
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,7 @@ FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_poo
694694
FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc
695695
FILE: ../../../flutter/shell/platform/android/flutter_main.cc
696696
FILE: ../../../flutter/shell/platform/android/flutter_main.h
697+
FILE: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java
697698
FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java
698699
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java
699700
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java

shell/platform/android/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ embedding_sources_jar_filename = "$embedding_artifact_id-sources.jar"
117117
embedding_source_jar_path = "$root_out_dir/$embedding_sources_jar_filename"
118118

119119
android_java_sources = [
120+
"io/flutter/FlutterInjector.java",
120121
"io/flutter/Log.java",
121122
"io/flutter/app/FlutterActivity.java",
122123
"io/flutter/app/FlutterActivityDelegate.java",
@@ -415,6 +416,7 @@ action("robolectric_tests") {
415416
jar_path = "$root_out_dir/robolectric_tests.jar"
416417

417418
sources = [
419+
"test/io/flutter/FlutterInjectorTest.java",
418420
"test/io/flutter/FlutterTestSuite.java",
419421
"test/io/flutter/SmokeTest.java",
420422
"test/io/flutter/embedding/android/AndroidKeyProcessorTest.java",
@@ -434,6 +436,7 @@ action("robolectric_tests") {
434436
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
435437
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
436438
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
439+
"test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java",
437440
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
438441
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
439442
"test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java",
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter;
6+
7+
import android.support.annotation.NonNull;
8+
import android.support.annotation.VisibleForTesting;
9+
import io.flutter.embedding.engine.loader.FlutterLoader;
10+
11+
/**
12+
* This class is a simple dependency injector for the relatively thin Android part of the Flutter
13+
* engine.
14+
*
15+
* <p>This simple solution is used facilitate testability without bringing in heavier
16+
* app-development centric dependency injection frameworks such as Guice or Dagger2 or spreading
17+
* construction injection everywhere.
18+
*/
19+
public final class FlutterInjector {
20+
21+
private static FlutterInjector instance;
22+
private static boolean accessed;
23+
24+
/**
25+
* Use {@link FlutterInjector.Builder} to specify members to be injected via the static {@code
26+
* FlutterInjector}.
27+
*
28+
* <p>This can only be called at the beginning of the program before the {@link #instance()} is
29+
* accessed.
30+
*/
31+
public static void setInstance(@NonNull FlutterInjector injector) {
32+
if (accessed) {
33+
throw new IllegalStateException(
34+
"Cannot change the FlutterInjector instance once it's been "
35+
+ "read. If you're trying to dependency inject, be sure to do so at the beginning of "
36+
+ "the program");
37+
}
38+
instance = injector;
39+
}
40+
41+
/**
42+
* Retrieve the static instance of the {@code FlutterInjector} to use in your program.
43+
*
44+
* <p>Once you access it, you can no longer change the values injected.
45+
*
46+
* <p>If no override is provided for the injector, reasonable defaults are provided.
47+
*/
48+
public static FlutterInjector instance() {
49+
accessed = true;
50+
if (instance == null) {
51+
instance = new Builder().build();
52+
}
53+
return instance;
54+
}
55+
56+
// This whole class is here to enable testing so to test the thing that lets you test, some degree
57+
// of hack is needed.
58+
@VisibleForTesting
59+
public static void reset() {
60+
accessed = false;
61+
instance = null;
62+
}
63+
64+
private FlutterInjector(boolean shouldLoadNative, @NonNull FlutterLoader flutterLoader) {
65+
this.shouldLoadNative = shouldLoadNative;
66+
this.flutterLoader = flutterLoader;
67+
}
68+
69+
private boolean shouldLoadNative;
70+
private FlutterLoader flutterLoader;
71+
72+
/**
73+
* Returns whether the Flutter Android engine embedding should load the native C++ engine.
74+
*
75+
* <p>Useful for testing since JVM tests via Robolectric can't load native libraries.
76+
*/
77+
public boolean shouldLoadNative() {
78+
return shouldLoadNative;
79+
}
80+
81+
/** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */
82+
@NonNull
83+
public FlutterLoader flutterLoader() {
84+
return flutterLoader;
85+
}
86+
87+
/**
88+
* Builder used to supply a custom FlutterInjector instance to {@link
89+
* FlutterInjector#setInstance(FlutterInjector)}.
90+
*
91+
* <p>Non-overriden values have reasonable defaults.
92+
*/
93+
public static final class Builder {
94+
95+
private boolean shouldLoadNative = true;
96+
/**
97+
* Sets whether the Flutter Android engine embedding should load the native C++ engine.
98+
*
99+
* <p>Useful for testing since JVM tests via Robolectric can't load native libraries.
100+
*
101+
* <p>Defaults to true.
102+
*/
103+
public Builder setShouldLoadNative(boolean shouldLoadNative) {
104+
this.shouldLoadNative = shouldLoadNative;
105+
return this;
106+
}
107+
108+
private FlutterLoader flutterLoader;
109+
/**
110+
* Sets a {@link FlutterLoader} override.
111+
*
112+
* <p>A reasonable default will be used if unspecified.
113+
*/
114+
public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) {
115+
this.flutterLoader = flutterLoader;
116+
return this;
117+
}
118+
119+
private void fillDefaults() {
120+
if (flutterLoader == null) {
121+
flutterLoader = new FlutterLoader();
122+
}
123+
}
124+
125+
/**
126+
* Builds a {@link FlutterInjector} from the builder. Unspecified properties will have
127+
* reasonable defaults.
128+
*/
129+
public FlutterInjector build() {
130+
fillDefaults();
131+
132+
System.out.println("should load native is " + shouldLoadNative);
133+
return new FlutterInjector(shouldLoadNative, flutterLoader);
134+
}
135+
}
136+
}

shell/platform/android/io/flutter/app/FlutterActivityDelegate.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
import java.util.ArrayList;
3636

3737
/**
38-
* Class that performs the actual work of tying Android {@link Activity} instances to Flutter.
38+
* Deprecated class that performs the actual work of tying Android {@link Activity} instances to
39+
* Flutter.
3940
*
4041
* <p>This exists as a dedicated class (as opposed to being integrated directly into {@link
4142
* FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}.
@@ -48,6 +49,10 @@
4849
* FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make
4950
* your activity implement {@link PluginRegistry} and/or {@link
5051
* io.flutter.view.FlutterView.Provider} and forward those methods to this class as well.
52+
*
53+
* <p>Deprecation: {@link io.flutter.embedding.android.FlutterActivity} is the new API that now
54+
* replaces this class and {@link io.flutter.app.FlutterActivity}. See
55+
* https://flutter.dev/go/android-project-migration for more migration details.
5156
*/
5257
public final class FlutterActivityDelegate
5358
implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry {

shell/platform/android/io/flutter/app/FlutterApplication.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import android.app.Activity;
88
import android.app.Application;
99
import androidx.annotation.CallSuper;
10-
import io.flutter.view.FlutterMain;
10+
import io.flutter.FlutterInjector;
1111

1212
/**
1313
* Flutter implementation of {@link android.app.Application}, managing application-level global
@@ -21,7 +21,7 @@ public class FlutterApplication extends Application {
2121
@CallSuper
2222
public void onCreate() {
2323
super.onCreate();
24-
FlutterMain.startInitialization(this);
24+
FlutterInjector.instance().flutterLoader().startInitialization(this);
2525
}
2626

2727
private Activity mCurrentActivity = null;

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.content.Context;
88
import androidx.annotation.NonNull;
99
import androidx.annotation.Nullable;
10+
import io.flutter.FlutterInjector;
1011
import io.flutter.Log;
1112
import io.flutter.embedding.engine.dart.DartExecutor;
1213
import io.flutter.embedding.engine.loader.FlutterLoader;
@@ -131,7 +132,8 @@ public void onPreEngineRestart() {
131132
* <p>In order to pass Dart VM initialization arguments (see {@link
132133
* io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the
133134
* initialization arguments by calling {@link FlutterLoader#startInitialization(Context)} and
134-
* {@link FlutterLoader#ensureInitializationComplete(Context, String[])}.
135+
* {@link FlutterLoader#ensureInitializationComplete(Context, String[])} before constructing the
136+
* engine.
135137
*/
136138
public FlutterEngine(@NonNull Context context) {
137139
this(context, null);
@@ -143,7 +145,7 @@ public FlutterEngine(@NonNull Context context) {
143145
* <p>If the Dart VM has already started, the given arguments will have no effect.
144146
*/
145147
public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) {
146-
this(context, FlutterLoader.getInstance(), new FlutterJNI(), dartVmArgs, true);
148+
this(context, /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, true);
147149
}
148150

149151
/**
@@ -158,7 +160,7 @@ public FlutterEngine(
158160
boolean automaticallyRegisterPlugins) {
159161
this(
160162
context,
161-
FlutterLoader.getInstance(),
163+
/* flutterLoader */ null,
162164
new FlutterJNI(),
163165
dartVmArgs,
164166
automaticallyRegisterPlugins);
@@ -189,7 +191,7 @@ public FlutterEngine(
189191
boolean waitForRestorationData) {
190192
this(
191193
context,
192-
FlutterLoader.getInstance(),
194+
/* flutterLoader */ null,
193195
new FlutterJNI(),
194196
new PlatformViewsController(),
195197
dartVmArgs,
@@ -206,7 +208,7 @@ public FlutterEngine(
206208
*/
207209
public FlutterEngine(
208210
@NonNull Context context,
209-
@NonNull FlutterLoader flutterLoader,
211+
@Nullable FlutterLoader flutterLoader,
210212
@NonNull FlutterJNI flutterJNI) {
211213
this(context, flutterLoader, flutterJNI, null, true);
212214
}
@@ -219,7 +221,7 @@ public FlutterEngine(
219221
*/
220222
public FlutterEngine(
221223
@NonNull Context context,
222-
@NonNull FlutterLoader flutterLoader,
224+
@Nullable FlutterLoader flutterLoader,
223225
@NonNull FlutterJNI flutterJNI,
224226
@Nullable String[] dartVmArgs,
225227
boolean automaticallyRegisterPlugins) {
@@ -238,7 +240,7 @@ public FlutterEngine(
238240
*/
239241
public FlutterEngine(
240242
@NonNull Context context,
241-
@NonNull FlutterLoader flutterLoader,
243+
@Nullable FlutterLoader flutterLoader,
242244
@NonNull FlutterJNI flutterJNI,
243245
@NonNull PlatformViewsController platformViewsController,
244246
@Nullable String[] dartVmArgs,
@@ -256,7 +258,7 @@ public FlutterEngine(
256258
/** Fully configurable {@code FlutterEngine} constructor. */
257259
public FlutterEngine(
258260
@NonNull Context context,
259-
@NonNull FlutterLoader flutterLoader,
261+
@Nullable FlutterLoader flutterLoader,
260262
@NonNull FlutterJNI flutterJNI,
261263
@NonNull PlatformViewsController platformViewsController,
262264
@Nullable String[] dartVmArgs,
@@ -280,6 +282,9 @@ public FlutterEngine(
280282
this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
281283

282284
this.flutterJNI = flutterJNI;
285+
if (flutterLoader == null) {
286+
flutterLoader = FlutterInjector.instance().flutterLoader();
287+
}
283288
flutterLoader.startInitialization(context.getApplicationContext());
284289
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
285290

shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
* <p>The term "shell" refers to the native code that adapts Flutter to different platforms.
1616
* Flutter's Android Java code initializes a native "shell" and passes these arguments to that
1717
* native shell when it is initialized. See {@link
18-
* io.flutter.view.FlutterMain#ensureInitializationComplete(Context, String[])} for more
19-
* information.
18+
* io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])}
19+
* for more information.
2020
*/
2121
@SuppressWarnings({"WeakerAccess", "unused"})
2222
public class FlutterShellArgs {

shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import androidx.annotation.NonNull;
99
import androidx.annotation.Nullable;
1010
import androidx.annotation.UiThread;
11+
import io.flutter.FlutterInjector;
1112
import io.flutter.Log;
1213
import io.flutter.embedding.engine.FlutterJNI;
14+
import io.flutter.embedding.engine.loader.FlutterLoader;
1315
import io.flutter.plugin.common.BinaryMessenger;
1416
import io.flutter.plugin.common.StringCodec;
1517
import io.flutter.view.FlutterCallbackInformation;
16-
import io.flutter.view.FlutterMain;
1718
import java.nio.ByteBuffer;
1819

1920
/**
@@ -250,9 +251,19 @@ public void notifyLowMemoryWarning() {
250251
* that entrypoint and other assets required for Dart execution.
251252
*/
252253
public static class DartEntrypoint {
254+
/**
255+
* Create a DartEntrypoint pointing to the default Flutter assets location with a default Dart
256+
* entrypoint.
257+
*/
253258
@NonNull
254259
public static DartEntrypoint createDefault() {
255-
return new DartEntrypoint(FlutterMain.findAppBundlePath(), "main");
260+
FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader();
261+
262+
if (!flutterLoader.initialized()) {
263+
throw new AssertionError(
264+
"DartEntrypoints can only be created once a FlutterEngine is created.");
265+
}
266+
return new DartEntrypoint(flutterLoader.findAppBundlePath(), "main");
256267
}
257268

258269
/** The path within the AssetManager where the app will look for assets. */

0 commit comments

Comments
 (0)