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: