Inject plugin registration. (#9216)
Added a PluginRegistry to the new project template. The registry files will be automatically updated at build time to register the native plugins.
Fixes #7814.
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 4c81774..a4e0942 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -156,7 +156,7 @@
settings.values['flutter.buildMode'] = buildModeName;
settings.writeContents(localProperties);
- writeFlutterPluginsList();
+ injectPlugins();
final String gradle = ensureGradle();
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 4a7427a..1dedb53 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -15,6 +15,7 @@
import '../flx.dart' as flx;
import '../globals.dart';
import '../ios/xcodeproj.dart';
+import '../plugins.dart';
import '../runner/flutter_command.dart';
import '../template.dart';
@@ -141,8 +142,10 @@
updateXcodeGeneratedProperties(appPath, BuildMode.debug, flx.defaultMainPath);
- if (argResults['pub'])
+ if (argResults['pub']) {
await pubGet(directory: appPath);
+ injectPlugins(directory: appPath);
+ }
printStatus('');
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 545fc3b..9baf97f 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -152,7 +152,7 @@
// copied over to a location that is suitable for Xcodebuild to find them.
final Directory appDirectory = fs.directory(app.appDirectory);
await _addServicesToBundle(appDirectory);
- writeFlutterPluginsList();
+ injectPlugins();
await _runPodInstall(appDirectory, flutterFrameworkDir(mode));
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 0df1721..9e8b6f3 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -2,46 +2,69 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:mustache/mustache.dart' as mustache;
import 'package:yaml/yaml.dart';
import 'base/file_system.dart';
import 'dart/package_map.dart';
import 'globals.dart';
-dynamic _loadYamlFile(String path) {
- if (!fs.isFileSync(path))
- return null;
- final String manifestString = fs.file(path).readAsStringSync();
- return loadYaml(manifestString);
+class Plugin {
+ final String name;
+ final String path;
+ final String pluginClass;
+ final String androidPackage;
+
+ Plugin(this.name, this.path, this.pluginClass, this.androidPackage);
+
+ factory Plugin.fromYaml(String name, String path, dynamic pluginYaml) {
+ String androidPackage;
+ String pluginClass;
+ if (pluginYaml != null) {
+ androidPackage = pluginYaml['androidPackage'];
+ pluginClass = pluginYaml['pluginClass'];
+ }
+ return new Plugin(name, path, pluginClass, androidPackage);
+ }
}
-String _generatePluginManifest() {
+Plugin _pluginFromPubspec(String name, Uri packageRoot) {
+ final String pubspecPath = packageRoot.resolve('pubspec.yaml').path;
+ if (!fs.isFileSync(pubspecPath))
+ return null;
+ final dynamic pubspec = loadYaml(fs.file(pubspecPath).readAsStringSync());
+ if (pubspec == null)
+ return null;
+ final dynamic flutterConfig = pubspec['flutter'];
+ if (flutterConfig == null || !flutterConfig.containsKey('plugin'))
+ return null;
+ printTrace('Found plugin $name at ${packageRoot.path}');
+ return new Plugin.fromYaml(name, packageRoot.path, flutterConfig['plugin']);
+}
+
+List<Plugin> _findPlugins(String directory) {
+ final List<Plugin> plugins = <Plugin>[];
Map<String, Uri> packages;
try {
- packages = new PackageMap(PackageMap.globalPackagesPath).map;
+ final String packagesFile = fs.path.join(directory, PackageMap.globalPackagesPath);
+ packages = new PackageMap(packagesFile).map;
} on FormatException catch(e) {
printTrace('Invalid .packages file: $e');
- return '';
+ return plugins;
}
- final List<String> plugins = <String>[];
packages.forEach((String name, Uri uri) {
final Uri packageRoot = uri.resolve('..');
- final dynamic packageConfig = _loadYamlFile(packageRoot.resolve('pubspec.yaml').path);
- if (packageConfig != null) {
- final dynamic flutterConfig = packageConfig['flutter'];
- if (flutterConfig != null && flutterConfig.containsKey('plugin')) {
- printTrace('Found plugin $name at ${packageRoot.path}');
- plugins.add('$name=${packageRoot.path}');
- }
- }
+ final Plugin plugin = _pluginFromPubspec(name, packageRoot);
+ if (plugin != null)
+ plugins.add(plugin);
});
- return plugins.join('\n');
+ return plugins;
}
-void writeFlutterPluginsList() {
- final File pluginsProperties = fs.file('.flutter-plugins');
-
- final String pluginManifest = _generatePluginManifest();
+void _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
+ final File pluginsProperties = fs.file(fs.path.join(directory, '.flutter-plugins'));
+ final String pluginManifest =
+ plugins.map((Plugin p) => '${p.name}=${p.path}').join('\n');
if (pluginManifest.isNotEmpty) {
pluginsProperties.writeAsStringSync('$pluginManifest\n');
} else {
@@ -50,3 +73,132 @@
}
}
}
+
+const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;
+
+import io.flutter.app.FlutterActivity;
+
+{{#plugins}}
+import {{package}}.{{class}};
+{{/plugins}}
+
+/**
+ * Generated file. Do not edit.
+ */
+
+public class PluginRegistry {
+{{#plugins}}
+ public {{class}} {{name}};
+{{/plugins}}
+
+ public void registerAll(FlutterActivity activity) {
+{{#plugins}}
+ {{name}} = {{class}}.register(activity);
+{{/plugins}}
+ }
+}
+''';
+
+void _writeAndroidPluginRegistry(String directory, List<Plugin> plugins) {
+ final List<Map<String, dynamic>> androidPlugins = plugins
+ .where((Plugin p) => p.androidPackage != null && p.pluginClass != null)
+ .map((Plugin p) => <String, dynamic>{
+ 'name': p.name,
+ 'package': p.androidPackage,
+ 'class': p.pluginClass,
+ })
+ .toList();
+ final Map<String, dynamic> context = <String, dynamic>{
+ 'plugins': androidPlugins,
+ };
+
+ final String pluginRegistry =
+ new mustache.Template(_androidPluginRegistryTemplate).renderString(context);
+ final String javaSourcePath = fs.path.join(directory, 'android', 'app', 'src', 'main', 'java');
+ final Directory registryDirectory =
+ fs.directory(fs.path.join(javaSourcePath, 'io', 'flutter', 'plugins'));
+ registryDirectory.createSync(recursive: true);
+ final File registryFile = registryDirectory.childFile('PluginRegistry.java');
+ registryFile.writeAsStringSync(pluginRegistry);
+}
+
+const String _iosPluginRegistryHeaderTemplate = '''//
+// Generated file. Do not edit.
+//
+
+#ifndef PluginRegistry_h
+#define PluginRegistry_h
+
+#import <Flutter/Flutter.h>
+
+{{#plugins}}
+#import "{{class}}.h"
+{{/plugins}}
+
+@interface PluginRegistry : NSObject
+
+{{#plugins}}
+@property (readonly, nonatomic) {{class}} *{{name}};
+{{/plugins}}
+
+- (instancetype)initWithController:(FlutterViewController *)controller;
+
+@end
+
+#endif /* PluginRegistry_h */
+''';
+
+const String _iosPluginRegistryImplementationTemplate = '''//
+// Generated file. Do not edit.
+//
+
+#import "PluginRegistry.h"
+
+@implementation PluginRegistry
+
+- (instancetype)initWithController:(FlutterViewController *)controller {
+ if (self = [super init]) {
+{{#plugins}}
+ _{{name}} = [[{{class}} alloc] initWithController:controller];
+{{/plugins}}
+ }
+ return self;
+}
+
+@end
+''';
+
+void _writeIOSPluginRegistry(String directory, List<Plugin> plugins) {
+ final List<Map<String, dynamic>> iosPlugins = plugins
+ .where((Plugin p) => p.pluginClass != null)
+ .map((Plugin p) => <String, dynamic>{
+ 'name': p.name,
+ 'class': p.pluginClass,
+ }).
+ toList();
+ final Map<String, dynamic> context = <String, dynamic>{
+ 'plugins': iosPlugins,
+ };
+
+ final String pluginRegistryHeader =
+ new mustache.Template(_iosPluginRegistryHeaderTemplate).renderString(context);
+ final String pluginRegistryImplementation =
+ new mustache.Template(_iosPluginRegistryImplementationTemplate).renderString(context);
+ final Directory registryDirectory = fs.directory(fs.path.join(directory, 'ios', 'Runner'));
+ registryDirectory.createSync(recursive: true);
+ final File registryHeaderFile = registryDirectory.childFile('PluginRegistry.h');
+ registryHeaderFile.writeAsStringSync(pluginRegistryHeader);
+ final File registryImplementationFile = registryDirectory.childFile('PluginRegistry.m');
+ registryImplementationFile.writeAsStringSync(pluginRegistryImplementation);
+
+}
+
+void injectPlugins({String directory}) {
+ directory ??= fs.currentDirectory.path;
+ final List<Plugin> plugins = _findPlugins(directory);
+ _writeFlutterPluginsList(directory, plugins);
+ if (fs.isDirectorySync(fs.path.join(directory, 'android')))
+ _writeAndroidPluginRegistry(directory, plugins);
+ if (fs.isDirectorySync(fs.path.join(directory, 'ios')))
+ _writeIOSPluginRegistry(directory, plugins);
+}
diff --git a/packages/flutter_tools/templates/create/android.tmpl/app/src/main/java/com/yourcompany/projectName/MainActivity.java.tmpl b/packages/flutter_tools/templates/create/android.tmpl/app/src/main/java/com/yourcompany/projectName/MainActivity.java.tmpl
index d6fceaf..c2b94c4 100644
--- a/packages/flutter_tools/templates/create/android.tmpl/app/src/main/java/com/yourcompany/projectName/MainActivity.java.tmpl
+++ b/packages/flutter_tools/templates/create/android.tmpl/app/src/main/java/com/yourcompany/projectName/MainActivity.java.tmpl
@@ -2,16 +2,15 @@
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
-{{#withPluginHook}}
-import {{androidPluginIdentifier}}.{{pluginClass}};
-{{/withPluginHook}}
+import io.flutter.plugins.PluginRegistry;
public class MainActivity extends FlutterActivity {
+ PluginRegistry pluginRegistry;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-{{#withPluginHook}}
- {{pluginClass}}.register(this);
-{{/withPluginHook}}
+ pluginRegistry = new PluginRegistry();
+ pluginRegistry.registerAll(this);
}
}
diff --git a/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
index da59258..1a1f18a 100644
--- a/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* PluginRegistry.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -39,6 +40,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* PluginRegistry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PluginRegistry.h; sourceTree = "<group>"; };
+ 1498D2331E8E89220040F4C2 /* PluginRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PluginRegistry.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
4D558BB7489B1C82B42A9097 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -121,6 +124,8 @@
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
+ 1498D2321E8E86230040F4C2 /* PluginRegistry.h */,
+ 1498D2331E8E89220040F4C2 /* PluginRegistry.m */,
);
path = Runner;
sourceTree = "<group>";
@@ -299,6 +304,7 @@
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
+ 1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m.tmpl b/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m
similarity index 82%
rename from packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m.tmpl
rename to packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m
index 6ba55b1..3b6ab51 100644
--- a/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m.tmpl
+++ b/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m
@@ -1,23 +1,16 @@
#include "AppDelegate.h"
-{{#withPluginHook}}
-#include "{{pluginClass}}.h"
+#include "PluginRegistry.h"
@implementation AppDelegate {
- {{pluginClass}} *_{{pluginProjectName}};
+ PluginRegistry *plugins;
}
-{{/withPluginHook}}
-{{^withPluginHook}}
-@implementation AppDelegate
-{{/withPluginHook}}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- // Override point for customization after application launch.
-{{#withPluginHook}}
+ // Override point for customization after application launch.
FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController;
- _{{pluginProjectName}} = [[{{pluginClass}} alloc] initWithController:flutterController];
-{{/withPluginHook}}
- return YES;
+ plugins = [[PluginRegistry alloc] initWithController:flutterController];
+ return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/src/main/java/com/yourcompany/projectName/pluginClass.java.tmpl b/packages/flutter_tools/templates/plugin/android.tmpl/src/main/java/com/yourcompany/projectName/pluginClass.java.tmpl
index 84b4999..1c4a967 100644
--- a/packages/flutter_tools/templates/plugin/android.tmpl/src/main/java/com/yourcompany/projectName/pluginClass.java.tmpl
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/src/main/java/com/yourcompany/projectName/pluginClass.java.tmpl
@@ -12,8 +12,8 @@
public class {{pluginClass}} implements MethodCallHandler {
private FlutterActivity activity;
- public static void register(FlutterActivity activity) {
- new {{pluginClass}}(activity);
+ public static {{pluginClass}} register(FlutterActivity activity) {
+ return new {{pluginClass}}(activity);
}
private {{pluginClass}}(FlutterActivity activity) {
diff --git a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
index 517bf38..d1747d7 100644
--- a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
@@ -3,6 +3,8 @@
flutter:
plugin:
+ androidPackage: {{androidIdentifier}}
+ pluginClass: {{pluginClass}}
dependencies:
flutter: