diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart index cfadd8f6431..07fc58e31a9 100644 --- a/packages/flutter_tools/lib/src/platform_plugins.dart +++ b/packages/flutter_tools/lib/src/platform_plugins.dart @@ -64,17 +64,20 @@ class AndroidPlugin extends PluginPlatform { 'name': name, 'package': package, 'class': pluginClass, - 'usesEmbedding2': _embeddingVersion == '2', + // Mustache doesn't support complex types. + 'supportsEmbeddingV1': _supportedEmbedings.contains('1'), + 'supportsEmbeddingV2': _supportedEmbedings.contains('2'), }; } - String _cachedEmbeddingVersion; + Set _cachedEmbeddingVersion; /// Returns the version of the Android embedding. - String get _embeddingVersion => _cachedEmbeddingVersion ??= _getEmbeddingVersion(); + Set get _supportedEmbedings => _cachedEmbeddingVersion ??= _getSupportedEmbeddings(); - String _getEmbeddingVersion() { + Set _getSupportedEmbeddings() { assert(pluginPath != null); + final Set supportedEmbeddings = {}; final String baseMainPath = fs.path.join( pluginPath, 'android', @@ -113,9 +116,15 @@ class AndroidPlugin extends PluginPlatform { } if (mainClassContent .contains('io.flutter.embedding.engine.plugins.FlutterPlugin')) { - return '2'; + supportedEmbeddings.add('2'); + } else { + supportedEmbeddings.add('1'); } - return '1'; + if (mainClassContent.contains('PluginRegistry') + && mainClassContent.contains('registerWith')) { + supportedEmbeddings.add('1'); + } + return supportedEmbeddings; } } diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 11dda77bd6e..613400cbfbc 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -337,12 +337,14 @@ public final class GeneratedPluginRegistrant { ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); {{/needsShim}} {{#plugins}} - {{#usesEmbedding2}} + {{#supportsEmbeddingV2}} flutterEngine.getPlugins().add(new {{package}}.{{class}}()); - {{/usesEmbedding2}} - {{^usesEmbedding2}} - {{package}}.{{class}}.registerWith(shimPluginRegistry.registrarFor("{{package}}.{{class}}")); - {{/usesEmbedding2}} + {{/supportsEmbeddingV2}} + {{^supportsEmbeddingV2}} + {{#supportsEmbeddingV1}} + {{package}}.{{class}}.registerWith(shimPluginRegistry.registrarFor("{{package}}.{{class}}")); + {{/supportsEmbeddingV1}} + {{/supportsEmbeddingV2}} {{/plugins}} } } @@ -396,14 +398,24 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List // If a plugin is using an embedding version older than 2.0 and the app is using 2.0, // then add shim for the old plugins. for (Map plugin in androidPlugins) { - if (!plugin['usesEmbedding2']) { + if (plugin['supportsEmbeddingV1'] && !plugin['supportsEmbeddingV2']) { templateContext['needsShim'] = true; break; } } templateContent = _androidPluginRegistryTemplateNewEmbedding; - break; + break; + case AndroidEmbeddingVersion.v1: default: + for (Map plugin in androidPlugins) { + if (!plugin['supportsEmbeddingV1'] && plugin['supportsEmbeddingV2']) { + throwToolExit( + 'The plugin `${plugin['name']}` requires your app to be migrated to ' + 'the Android embedding v2. Follow the steps on https://flutter.dev/go/android-project-migration ' + 'and re-run this command.' + ); + } + } templateContent = _androidPluginRegistryTemplateOldEmbedding; break; } diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index fe45f10c727..ea41d4db2ef 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -265,6 +265,100 @@ plugin3:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()} XcodeProjectInterpreter: () => xcodeProjectInterpreter, }); + testUsingContext('exits the tool if an app uses the v1 embedding and a plugin only supports the v2 embedding', () async { + when(flutterProject.isModule).thenReturn(false); + when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1); + + final Directory pluginUsingJavaAndNewEmbeddingDir = + fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); + pluginUsingJavaAndNewEmbeddingDir + .childFile('pubspec.yaml') + .writeAsStringSync(''' + flutter: + plugin: + androidPackage: plugin1 + pluginClass: UseNewEmbedding + '''); + pluginUsingJavaAndNewEmbeddingDir + .childDirectory('android') + .childDirectory('src') + .childDirectory('main') + .childDirectory('java') + .childDirectory('plugin1') + .childFile('UseNewEmbedding.java') + ..createSync(recursive: true) + ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;'); + + flutterProject.directory + .childFile('.packages') + .writeAsStringSync(''' +plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()} +'''); + await expectLater( + () async { + await injectPlugins(flutterProject); + }, + throwsToolExit( + message: 'The plugin `plugin1` requires your app to be migrated to the Android embedding v2. ' + 'Follow the steps on https://flutter.dev/go/android-project-migration and re-run this command.' + ), + ); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + FeatureFlags: () => featureFlags, + XcodeProjectInterpreter: () => xcodeProjectInterpreter, + }); + + testUsingContext('allows app use a plugin that supports v1 and v2 embedding', () async { + when(flutterProject.isModule).thenReturn(false); + when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1); + + final Directory pluginUsingJavaAndNewEmbeddingDir = + fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); + pluginUsingJavaAndNewEmbeddingDir + .childFile('pubspec.yaml') + .writeAsStringSync(''' + flutter: + plugin: + androidPackage: plugin1 + pluginClass: UseNewEmbedding + '''); + pluginUsingJavaAndNewEmbeddingDir + .childDirectory('android') + .childDirectory('src') + .childDirectory('main') + .childDirectory('java') + .childDirectory('plugin1') + .childFile('UseNewEmbedding.java') + ..createSync(recursive: true) + ..writeAsStringSync( + 'import io.flutter.embedding.engine.plugins.FlutterPlugin;\n' + 'PluginRegistry\n' + 'registerWith(Irrelevant registrar)\n' + ); + + flutterProject.directory + .childFile('.packages') + .writeAsStringSync(''' +plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()} +'''); + await injectPlugins(flutterProject); + + final File registrant = flutterProject.directory + .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins')) + .childFile('GeneratedPluginRegistrant.java'); + + expect(registrant.existsSync(), isTrue); + expect(registrant.readAsStringSync(), contains('package io.flutter.plugins')); + expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + FeatureFlags: () => featureFlags, + XcodeProjectInterpreter: () => xcodeProjectInterpreter, + }); + testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async { when(flutterProject.isModule).thenReturn(false); when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);