diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3d7f197dcfeeb..389d3af06afdb 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -20,6 +20,7 @@ import io.flutter.embedding.engine.plugins.broadcastreceiver.BroadcastReceiverControlSurface; import io.flutter.embedding.engine.plugins.contentprovider.ContentProviderControlSurface; import io.flutter.embedding.engine.plugins.service.ServiceControlSurface; +import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; @@ -36,7 +37,6 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; -import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; @@ -342,7 +342,7 @@ public FlutterEngine( // Only automatically register plugins if both constructor parameter and // loaded AndroidManifest config turn this feature on. if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) { - registerPlugins(); + GeneratedPluginRegister.registerGeneratedPlugins(this); } } @@ -391,36 +391,6 @@ private boolean isAttachedToJni() { newFlutterJNI); // FlutterJNI. } - /** - * Registers all plugins that an app lists in its pubspec.yaml. - * - *
The Flutter tool generates a class called GeneratedPluginRegistrant, which includes the code - * necessary to register every plugin in the pubspec.yaml with a given {@code FlutterEngine}. The - * GeneratedPluginRegistrant must be generated per app, because each app uses different sets of - * plugins. Therefore, the Android embedding cannot place a compile-time dependency on this - * generated class. This method uses reflection to attempt to locate the generated file and then - * use it at runtime. - * - *
This method fizzles if the GeneratedPluginRegistrant cannot be found or invoked. This
- * situation should never occur, but if any eventuality comes up that prevents an app from using
- * this behavior, that app can still write code that explicitly registers plugins.
- */
- private void registerPlugins() {
- try {
- Class> generatedPluginRegistrant =
- Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
- Method registrationMethod =
- generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
- registrationMethod.invoke(null, this);
- } catch (Exception e) {
- Log.w(
- TAG,
- "Tried to automatically register plugins with FlutterEngine ("
- + this
- + ") but could not find and invoke the GeneratedPluginRegistrant.");
- }
- }
-
/**
* Cleans up all components within this {@code FlutterEngine} and destroys the associated Dart
* Isolate. All state held by the Dart Isolate, such as the Flutter Elements tree, is lost.
diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java b/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java
index 5bb0ff5a6f37b..fab7eb8c7ee65 100644
--- a/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java
+++ b/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java
@@ -33,11 +33,15 @@ public static void registerGeneratedPlugins(@NonNull FlutterEngine flutterEngine
generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
registrationMethod.invoke(null, flutterEngine);
} catch (Exception e) {
- Log.w(
+ Log.e(
TAG,
"Tried to automatically register plugins with FlutterEngine ("
+ flutterEngine
+ ") but could not find and invoke the GeneratedPluginRegistrant.");
+ Log.e(
+ TAG,
+ // getCause here because the first layer of the exception would be from reflect.
+ "Received exception while registering: " + e.getCause());
}
}
}
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 bcfabf863dafe..1af79de943259 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
@@ -35,6 +35,7 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
@@ -63,6 +64,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
@After
public void tearDown() {
GeneratedPluginRegistrant.clearRegisteredEngines();
+ // Make sure to not forget to remove the mock exception in the generated plugin registration
+ // mock, or everything subsequent will break.
+ GeneratedPluginRegistrant.pluginRegistrationException = null;
}
@Test
@@ -78,6 +82,38 @@ public void itAutomaticallyRegistersPluginsByDefault() {
assertEquals(flutterEngine, registeredEngines.get(0));
}
+ // Helps show the root cause of MissingPluginException type errors like
+ // https://github.com/flutter/flutter/issues/78625.
+ @Test
+ public void itCatchesAndDisplaysRegistrationExceptions() {
+ assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
+ GeneratedPluginRegistrant.pluginRegistrationException =
+ new RuntimeException("I'm a bug in the plugin");
+ FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
+ when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
+ FlutterEngine flutterEngine =
+ new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, flutterJNI);
+
+ List