Add template for plugin projects. (#9076)

Plugin projects are created by running `flutter create --plugin <name>`.

An example app is also created in the plugin project, using the normal 'create' template, which has been modified to allow for conditional plugin code.

Modified the android package name to match package naming conventions (all lower-case, and must match the directory name).
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 902670c..4a7427a 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -30,6 +30,12 @@
       defaultsTo: false,
       help: 'Also add a flutter_driver dependency and generate a sample \'flutter drive\' test.'
     );
+    argParser.addFlag(
+      'plugin',
+      negatable: true,
+      defaultsTo: false,
+      help: 'Generate a new Flutter Plugin project.'
+    );
     argParser.addOption(
       'description',
       defaultsTo: 'A new flutter project.',
@@ -80,9 +86,10 @@
     if (!fs.isFileSync(fs.path.join(flutterDriverPackagePath, 'pubspec.yaml')))
       throwToolExit('Unable to find package:flutter_driver in $flutterDriverPackagePath', exitCode: 2);
 
+    final bool generatePlugin = argResults['plugin'];
+
     final Directory projectDir = fs.directory(argResults.rest.first);
     final String dirPath = fs.path.normalize(projectDir.absolute.path);
-    final String relativePath = fs.path.relative(dirPath);
     final String projectName = _normalizeProjectName(fs.path.basename(dirPath));
 
     String error =_validateProjectDir(dirPath, flutterRoot: flutterRoot);
@@ -93,24 +100,55 @@
     if (error != null)
       throwToolExit(error);
 
-    final int generatedCount = _renderTemplates(
-      projectName,
-      argResults['description'],
-      dirPath,
-      flutterPackagesDirectory,
-      renderDriverTest: argResults['with-driver-test']
+    final Map<String, dynamic> templateContext = _templateContext(
+        projectName, argResults['description'], dirPath,
+        flutterPackagesDirectory, renderDriverTest: argResults['with-driver-test'],
+        withPluginHook: generatePlugin,
     );
+
+    printStatus('Creating project ${fs.path.relative(dirPath)}...');
+    int generatedCount = 0;
+    String appPath = dirPath;
+    if (generatePlugin) {
+      final String description = argResults.wasParsed('description')
+          ? argResults['description']
+          : 'A new flutter plugin project.';
+      templateContext['description'] = description;
+      generatedCount += _renderTemplate('plugin', dirPath, templateContext);
+
+      if (argResults['pub'])
+        await pubGet(directory: dirPath);
+
+      appPath = fs.path.join(dirPath, 'example');
+      final String androidPluginIdentifier = templateContext['androidIdentifier'];
+      final String exampleProjectName = projectName + '_example';
+      templateContext['projectName'] = exampleProjectName;
+      templateContext['androidIdentifier'] = _createAndroidIdentifier(exampleProjectName);
+      templateContext['iosIdentifier'] = _createUTIIdentifier(exampleProjectName);
+      templateContext['description'] = 'Demonstrates how to use the $projectName plugin.';
+      templateContext['pluginProjectName'] = projectName;
+      templateContext['androidPluginIdentifier'] = androidPluginIdentifier;
+    }
+
+    generatedCount += _renderTemplate('create', appPath, templateContext);
+    if (argResults['with-driver-test']) {
+      final String testPath = fs.path.join(appPath, 'test_driver');
+      generatedCount += _renderTemplate('driver', testPath, templateContext);
+    }
+
     printStatus('Wrote $generatedCount files.');
     printStatus('');
 
-    updateXcodeGeneratedProperties(dirPath, BuildMode.debug, flx.defaultMainPath);
+    updateXcodeGeneratedProperties(appPath, BuildMode.debug, flx.defaultMainPath);
 
     if (argResults['pub'])
-      await pubGet(directory: dirPath);
+      await pubGet(directory: appPath);
 
     printStatus('');
 
     // Run doctor; tell the user the next steps.
+    final String relativeAppPath = fs.path.relative(appPath);
+    final String relativePluginPath = fs.path.relative(dirPath);
     if (doctor.canLaunchAnything) {
       // Let them know a summary of the state of their tooling.
       await doctor.summary();
@@ -118,11 +156,18 @@
       printStatus('''
 All done! In order to run your application, type:
 
-  \$ cd $relativePath
+  \$ cd $relativeAppPath
   \$ flutter run
 
-Your main program file is lib/main.dart in the $relativePath directory.
+Your main program file is lib/main.dart in the $relativeAppPath directory.
 ''');
+      if (generatePlugin) {
+        printStatus('''
+Your plugin code is in lib/$projectName.dart in the $relativePluginPath directory.
+
+Host platform code is in the android/ and ios/ directories under $relativePluginPath.
+''');
+      }
     } else {
       printStatus("You'll need to install additional components before you can run "
         "your Flutter app:");
@@ -133,48 +178,40 @@
       printStatus('');
       printStatus("After installing components, run 'flutter doctor' in order to "
         "re-validate your setup.");
-      printStatus("When complete, type 'flutter run' from the '$relativePath' "
+      printStatus("When complete, type 'flutter run' from the '$relativeAppPath' "
         "directory in order to launch your app.");
-      printStatus("Your main program file is: $relativePath/lib/main.dart");
+      printStatus("Your main program file is: $relativeAppPath/lib/main.dart");
     }
   }
 
-  int _renderTemplates(String projectName, String projectDescription, String dirPath,
-      String flutterPackagesDirectory, { bool renderDriverTest: false }) {
-    fs.directory(dirPath).createSync(recursive: true);
-
+  Map<String, dynamic> _templateContext(String projectName,
+      String projectDescription, String dirPath, String flutterPackagesDirectory,
+      { bool renderDriverTest: false, bool withPluginHook: false }) {
     flutterPackagesDirectory = fs.path.normalize(flutterPackagesDirectory);
     flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory);
 
-    printStatus('Creating project ${fs.path.relative(dirPath)}...');
+    final String pluginDartClass = _createPluginClassName(projectName);
+    final String pluginClass = pluginDartClass.endsWith('Plugin')
+        ? pluginDartClass
+        : pluginDartClass + 'Plugin';
 
-    final Map<String, dynamic> templateContext = <String, dynamic>{
+    return <String, dynamic>{
       'projectName': projectName,
       'androidIdentifier': _createAndroidIdentifier(projectName),
       'iosIdentifier': _createUTIIdentifier(projectName),
       'description': projectDescription,
       'flutterPackagesDirectory': flutterPackagesDirectory,
-      'androidMinApiLevel': android.minApiLevel
+      'androidMinApiLevel': android.minApiLevel,
+      'withDriverTest': renderDriverTest,
+      'pluginClass': pluginClass,
+      'pluginDartClass': pluginDartClass,
+      'withPluginHook': withPluginHook,
     };
+  }
 
-    int fileCount = 0;
-
-    templateContext['withDriverTest'] = renderDriverTest;
-
-    final Template createTemplate = new Template.fromName('create');
-    fileCount += createTemplate.render(
-      fs.directory(dirPath),
-      templateContext, overwriteExisting: false,
-      projectName: projectName
-    );
-
-    if (renderDriverTest) {
-      final Template driverTemplate = new Template.fromName('driver');
-      fileCount += driverTemplate.render(fs.directory(fs.path.join(dirPath, 'test_driver')),
-          templateContext, overwriteExisting: false);
-    }
-
-    return fileCount;
+  int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) {
+    final Template template = new Template.fromName(templateName);
+    return template.render(fs.directory(dirPath), context, overwriteExisting: false);
   }
 }
 
@@ -187,7 +224,12 @@
 }
 
 String _createAndroidIdentifier(String name) {
-  return 'com.yourcompany.${camelCase(name)}';
+  return 'com.yourcompany.$name';
+}
+
+String _createPluginClassName(String name) {
+  final String camelizedName = camelCase(name);
+  return camelizedName[0].toUpperCase() + camelizedName.substring(1);
 }
 
 String _createUTIIdentifier(String name) {
diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart
index abf7731..60b2223 100644
--- a/packages/flutter_tools/lib/src/template.dart
+++ b/packages/flutter_tools/lib/src/template.dart
@@ -62,11 +62,12 @@
     Directory destination,
     Map<String, dynamic> context, {
     bool overwriteExisting: true,
-    String projectName
   }) {
     destination.createSync(recursive: true);
     int fileCount = 0;
 
+    final String projectName = context['projectName'];
+    final String pluginClass = context['pluginClass'];
     final String destinationDirPath = destination.absolute.path;
 
     _templateFilePaths.forEach((String relativeDestPath, String absoluteSrcPath) {
@@ -76,6 +77,8 @@
           .replaceAll(_kTemplateExtension, '');
       if (projectName != null)
         finalDestinationPath = finalDestinationPath.replaceAll('projectName', projectName);
+      if (pluginClass != null)
+        finalDestinationPath = finalDestinationPath.replaceAll('pluginClass', pluginClass);
       final File finalDestinationFile = fs.file(finalDestinationPath);
       final String relativePathForLogging = fs.path.relative(finalDestinationFile.path);
 
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 5d50333..d6fceaf 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,11 +2,16 @@
 
 import android.os.Bundle;
 import io.flutter.app.FlutterActivity;
+{{#withPluginHook}}
+import {{androidPluginIdentifier}}.{{pluginClass}};
+{{/withPluginHook}}
 
 public class MainActivity extends FlutterActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+{{#withPluginHook}}
+        {{pluginClass}}.register(this);
+{{/withPluginHook}}
     }
 }
-
diff --git a/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m b/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m.tmpl
similarity index 81%
rename from packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m
rename to packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m.tmpl
index 4b3623e..de5dd48 100644
--- a/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m
+++ b/packages/flutter_tools/templates/create/ios.tmpl/Runner/AppDelegate.m.tmpl
@@ -1,9 +1,22 @@
 #include "AppDelegate.h"
+{{#withPluginHook}}
+#include "{{pluginClass}}.h"
 
+@implementation AppDelegate {
+  {{pluginClass}} *_{{pluginProjectName}};
+}
+{{/withPluginHook}}
+{{^withPluginHook}}
 @implementation AppDelegate
+{{/withPluginHook}}
 
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     // Override point for customization after application launch.
+{{#withPluginHook}}
+  FlutterViewController *flutterController =
+      (FlutterViewController *)self.window.rootViewController;
+  _{{pluginProjectName}} = [[{{pluginClass}} alloc] initWithFlutterView:flutterController];
+{{/withPluginHook}}
     return YES;
 }
 
diff --git a/packages/flutter_tools/templates/create/lib/main.dart.tmpl b/packages/flutter_tools/templates/create/lib/main.dart.tmpl
index d8cdd35..ff923fc 100644
--- a/packages/flutter_tools/templates/create/lib/main.dart.tmpl
+++ b/packages/flutter_tools/templates/create/lib/main.dart.tmpl
@@ -2,6 +2,9 @@
 {{#withDriverTest}}
 import 'package:flutter_driver/driver_extension.dart';
 {{/withDriverTest}}
+{{#withPluginHook}}
+import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart';
+{{/withPluginHook}}
 
 void main() {
 {{#withDriverTest}}
@@ -55,6 +58,19 @@
 
 class _MyHomePageState extends State<MyHomePage> {
   int _counter = 0;
+{{#withPluginHook}}
+  String _platformVersion = 'Unknown';
+
+  @override
+  void initState() {
+    super.initState();
+    {{pluginDartClass}}.platformVersion.then((String platformVersion) {
+      setState(() {
+        _platformVersion = platformVersion;
+      });
+    });
+  }
+{{/withPluginHook}}
 
   void _incrementCounter() {
     setState(() {
@@ -84,9 +100,21 @@
         title: new Text(config.title),
       ),
       body: new Center(
+{{#withPluginHook}}
+        child: new Column(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            new Text('Running on: $_platformVersion\n'),
+            new Text(
+                'Button tapped $_counter time${ _counter == 1 ? '' : 's' }.'),
+          ],
+        ),
+{{/withPluginHook}}
+{{^withPluginHook}}
         child: new Text(
           'Button tapped $_counter time${ _counter == 1 ? '' : 's' }.',
         ),
+{{/withPluginHook}}
       ),
       floatingActionButton: new FloatingActionButton(
         onPressed: _incrementCounter,
diff --git a/packages/flutter_tools/templates/create/pubspec.yaml.tmpl b/packages/flutter_tools/templates/create/pubspec.yaml.tmpl
index 44fae0f..6fac9b4 100644
--- a/packages/flutter_tools/templates/create/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/create/pubspec.yaml.tmpl
@@ -9,6 +9,10 @@
   flutter_driver:
     sdk: flutter
 {{/withDriverTest}}
+{{#withPluginHook}}
+  {{pluginProjectName}}:
+    path: ../
+{{/withPluginHook}}
 
 # For information on the generic Dart part of this file, see the
 # following page: https://www.dartlang.org/tools/pub/pubspec
diff --git a/packages/flutter_tools/templates/plugin/.gitignore.tmpl b/packages/flutter_tools/templates/plugin/.gitignore.tmpl
new file mode 100644
index 0000000..14c7d4c
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/.gitignore.tmpl
@@ -0,0 +1,9 @@
+.DS_Store
+.atom/
+.idea
+.packages
+.pub/
+build/
+ios/.generated/
+packages
+pubspec.lock
diff --git a/packages/flutter_tools/templates/plugin/LICENSE.tmpl b/packages/flutter_tools/templates/plugin/LICENSE.tmpl
new file mode 100644
index 0000000..86928f6
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/LICENSE.tmpl
@@ -0,0 +1,27 @@
+// Copyright 2017 Your Company. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Your Company nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/flutter_tools/templates/plugin/README.md.tmpl b/packages/flutter_tools/templates/plugin/README.md.tmpl
new file mode 100644
index 0000000..db00485
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/README.md.tmpl
@@ -0,0 +1,8 @@
+# {{projectName}}
+
+{{description}}
+
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](http://flutter.io/).
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore b/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore
new file mode 100644
index 0000000..5c4ef82
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/.gitignore
@@ -0,0 +1,12 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+
+/gradle
+/gradlew
+/gradlew.bat
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android.tmpl/build.gradle.tmpl
new file mode 100644
index 0000000..80eab06
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/build.gradle.tmpl
@@ -0,0 +1,32 @@
+group '{{androidIdentifier}}'
+version '1.0-SNAPSHOT'
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.0'
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 25
+    buildToolsVersion '25.0.0'
+
+    defaultConfig {
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    lintOptions {
+        disable 'InvalidPackage'
+    }
+}
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/gradle.properties b/packages/flutter_tools/templates/plugin/android.tmpl/gradle.properties
new file mode 100644
index 0000000..8bd86f6
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx1536M
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/settings.gradle.tmpl b/packages/flutter_tools/templates/plugin/android.tmpl/settings.gradle.tmpl
new file mode 100644
index 0000000..ad71a66
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/settings.gradle.tmpl
@@ -0,0 +1 @@
+rootProject.name = '{{projectName}}'
diff --git a/packages/flutter_tools/templates/plugin/android.tmpl/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/plugin/android.tmpl/src/main/AndroidManifest.xml.tmpl
new file mode 100644
index 0000000..d976a6d
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/src/main/AndroidManifest.xml.tmpl
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="{{androidIdentifier}}"
+  android:versionCode="1"
+  android:versionName="0.0.1">
+
+  <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
+</manifest>
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
new file mode 100644
index 0000000..906d240
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/android.tmpl/src/main/java/com/yourcompany/projectName/pluginClass.java.tmpl
@@ -0,0 +1,35 @@
+package {{androidIdentifier}};
+
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugin.common.FlutterMethodChannel;
+import io.flutter.plugin.common.FlutterMethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.FlutterMethodChannel.Response;
+import io.flutter.plugin.common.MethodCall;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {{pluginClass}}
+ */
+public class {{pluginClass}} implements MethodCallHandler {
+  private FlutterActivity activity;
+
+  public static void register(FlutterActivity activity) {
+    new {{pluginClass}}(activity);
+  }
+
+  private {{pluginClass}}(FlutterActivity activity) {
+    this.activity = activity;
+    new FlutterMethodChannel(activity.getFlutterView(), "{{projectName}}").setMethodCallHandler(this);
+  }
+
+  @Override
+  public void onMethodCall(MethodCall call, Response response) {
+    if (call.method.equals("getPlatformVersion")) {
+      response.success("Android " + android.os.Build.VERSION.RELEASE);
+    } else {
+      throw new IllegalArgumentException("Unknown method " + call.method);
+    }
+  }
+}
diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/.gitignore b/packages/flutter_tools/templates/plugin/ios.tmpl/.gitignore
new file mode 100644
index 0000000..956c87f
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/ios.tmpl/.gitignore
@@ -0,0 +1,31 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/Assets/.gitkeep b/packages/flutter_tools/templates/plugin/ios.tmpl/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/ios.tmpl/Assets/.gitkeep
diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/Classes/pluginClass.h.tmpl b/packages/flutter_tools/templates/plugin/ios.tmpl/Classes/pluginClass.h.tmpl
new file mode 100644
index 0000000..e412050
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/ios.tmpl/Classes/pluginClass.h.tmpl
@@ -0,0 +1,5 @@
+#import <Flutter/Flutter.h>
+
+@interface {{pluginClass}} : NSObject
+- initWithFlutterView:(FlutterViewController *)flutterView;
+@end
diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/Classes/pluginClass.m.tmpl b/packages/flutter_tools/templates/plugin/ios.tmpl/Classes/pluginClass.m.tmpl
new file mode 100644
index 0000000..eead0ec
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/ios.tmpl/Classes/pluginClass.m.tmpl
@@ -0,0 +1,25 @@
+#import "{{pluginClass}}.h"
+
+@implementation {{pluginClass}} {
+}
+
+- (instancetype)initWithFlutterView:(FlutterViewController *)flutterView {
+  self = [super init];
+  if (self) {
+    FlutterMethodChannel *channel = [FlutterMethodChannel
+        methodChannelNamed:@"{{projectName}}"
+           binaryMessenger:flutterView
+                     codec:[FlutterStandardMethodCodec sharedInstance]];
+    [channel setMethodCallHandler:^(FlutterMethodCall *call,
+                                    FlutterResultReceiver result) {
+      if ([@"getPlatformVersion" isEqualToString:call.method]) {
+        result([@"iOS " stringByAppendingString:[[UIDevice currentDevice]
+                                                    systemVersion]],
+               nil);
+      }
+    }];
+  }
+  return self;
+}
+
+@end
diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl b/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl
new file mode 100644
index 0000000..7939f26
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl
@@ -0,0 +1,19 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
+#
+Pod::Spec.new do |s|
+  s.name             = '{{projectName}}'
+  s.version          = '0.0.1'
+  s.summary          = '{{description}}'
+  s.description      = <<-DESC
+{{description}}
+                       DESC
+  s.homepage         = 'http://example.com'
+  s.license          = { :file => '../LICENSE' }
+  s.author           = { 'Your Company' => 'email@example.com' }
+  s.source           = { :path => '.' }
+  s.source_files = 'Classes/**/*'
+  s.public_header_files = 'Classes/**/*.h'
+  s.dependency 'Flutter'
+end
+
diff --git a/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl b/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl
new file mode 100644
index 0000000..0350ecd
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl
@@ -0,0 +1,11 @@
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+
+class {{pluginDartClass}} {
+  static const PlatformMethodChannel _channel =
+      const PlatformMethodChannel('{{projectName}}');
+
+  static Future<String> get platformVersion =>
+      _channel.invokeMethod('getPlatformVersion');
+}
diff --git a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
new file mode 100644
index 0000000..517bf38
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
@@ -0,0 +1,9 @@
+name: {{projectName}}
+description: {{description}}
+
+flutter:
+  plugin:
+
+dependencies:
+  flutter:
+    sdk: flutter