Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -342,7 +343,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);
}
}

Expand Down Expand Up @@ -391,36 +392,6 @@ private boolean isAttachedToJni() {
newFlutterJNI); // FlutterJNI.
}

/**
* Registers all plugins that an app lists in its pubspec.yaml.
*
* <p>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.
*
* <p>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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we could print out the name of the plugin that created the problem too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, I'd like to buff up the actual content of the method being called by reflection too. Though that's in the flutter/flutter repo (that Java file is made by flutter_tools). Right now, I think it does state which plugin caused the problem (in the exception call stack which has the plugin's package name in it).

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -78,6 +82,29 @@ 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<FlutterEngine> registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines();
// When it crashes, it doesn't end up registering anything.
assertEquals(0, registeredEngines.size());

// Check the logs actually says registration failed, so a subsequent MissingPluginException
// isn't mysterious.
assertTrue(ShadowLog.getLogsForTag("GeneratedPluginsRegister").get(0).msg.contains("Tried to automatically register plugins"));
assertTrue(ShadowLog.getLogsForTag("GeneratedPluginsRegister").get(1).msg.contains("I'm a bug in the plugin"));

GeneratedPluginRegistrant.pluginRegistrationException = null;
}

@Test
public void itDoesNotAutomaticallyRegistersPluginsWhenFlutterLoaderDisablesIt() {
assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
Expand Down Expand Up @@ -105,17 +132,7 @@ public void itDoesNotAutomaticallyRegistersPluginsWhenFlutterEngineDisablesIt()
assertTrue(registeredEngines.isEmpty());
}

@Test
public void itCanBeConfiguredToNotAutomaticallyRegisterPlugins() {
new FlutterEngine(
RuntimeEnvironment.application,
mock(FlutterLoader.class),
flutterJNI,
/*dartVmArgs=*/ new String[] {},
/*automaticallyRegisterPlugins=*/ false);

assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
}

@Test
public void itNotifiesPlatformViewsControllerWhenDevHotRestart() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@VisibleForTesting
public class GeneratedPluginRegistrant {
private static final List<FlutterEngine> registeredEngines = new ArrayList<>();
public static RuntimeException pluginRegistrationException;

/**
* The one and only method currently generated by the tool.
Expand All @@ -21,6 +22,9 @@ public class GeneratedPluginRegistrant {
* all registered engines instead.
*/
public static void registerWith(FlutterEngine engine) {
if (pluginRegistrationException != null) {
throw pluginRegistrationException;
}
registeredEngines.add(engine);
}

Expand Down