Merge pull request #2673 from jason-simmons/gradle_example

Example that builds a Flutter Android app using Gradle
diff --git a/examples/hello_android/README.md b/examples/hello_android/README.md
new file mode 100644
index 0000000..c7d64c4
--- /dev/null
+++ b/examples/hello_android/README.md
@@ -0,0 +1,16 @@
+# Example of building a Flutter app for Android using Gradle
+
+This project demonstrates how to embed Flutter within an Android application
+and build the Android and Flutter components with Gradle.
+
+To build this project:
+
+* Create a `local.properties` file with these entries:
+  * `sdk.dir=[path to the Android SDK]`
+  * `flutter.sdk=[path to the Flutter SDK]`
+  * `flutter.jar=[path to the flutter.jar file in your build of the Flutter engine]`
+
+Then run:
+
+* `gradle wrapper`
+* `./gradlew build`
diff --git a/examples/hello_android/app/build.gradle b/examples/hello_android/app/build.gradle
new file mode 100644
index 0000000..8ece56f
--- /dev/null
+++ b/examples/hello_android/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+apply plugin: 'flutter'
+
+android {
+    compileSdkVersion 22
+    buildToolsVersion '22.0.1'
+
+    lintOptions {
+        disable 'InvalidPackage'
+    }
+}
+
+flutter {
+    source 'src/flutter'
+}
diff --git a/examples/hello_android/app/src/flutter/lib/main.dart b/examples/hello_android/app/src/flutter/lib/main.dart
new file mode 100644
index 0000000..151b1da
--- /dev/null
+++ b/examples/hello_android/app/src/flutter/lib/main.dart
@@ -0,0 +1,7 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+
+void main() => runApp(new Center(child: new Text('Hello from Flutter!')));
diff --git a/examples/hello_android/app/src/flutter/pubspec.yaml b/examples/hello_android/app/src/flutter/pubspec.yaml
new file mode 100644
index 0000000..5669909
--- /dev/null
+++ b/examples/hello_android/app/src/flutter/pubspec.yaml
@@ -0,0 +1,4 @@
+name: gradle
+dependencies:
+  flutter:
+    path: ../../../../../packages/flutter
diff --git a/examples/hello_android/app/src/main/AndroidManifest.xml b/examples/hello_android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a272e14
--- /dev/null
+++ b/examples/hello_android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.flutter"
+    android:versionCode="1"
+    android:versionName="1.0.0" >
+
+    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <application android:name="org.domokit.sky.shell.SkyApplication" android:label="@string/app_name" >
+        <activity
+            android:name=".FlutterActivity"
+            android:label="@string/app_name"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
+            android:launchMode="singleTask"
+            android:theme="@android:style/Theme.Black.NoTitleBar"
+            android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/examples/hello_android/app/src/main/java/com/example/flutter/FlutterActivity.java b/examples/hello_android/app/src/main/java/com/example/flutter/FlutterActivity.java
new file mode 100644
index 0000000..7323d6f
--- /dev/null
+++ b/examples/hello_android/app/src/main/java/com/example/flutter/FlutterActivity.java
@@ -0,0 +1,39 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package com.example.flutter;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import org.chromium.base.PathUtils;
+import org.domokit.sky.shell.SkyApplication;
+import org.domokit.sky.shell.SkyMain;
+import org.domokit.sky.shell.PlatformViewAndroid;
+
+import java.io.File;
+
+public class FlutterActivity extends Activity {
+    private PlatformViewAndroid flutterView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        SkyMain.ensureInitialized(getApplicationContext(), null);
+        setContentView(R.layout.flutter_layout);
+
+        flutterView = (PlatformViewAndroid) findViewById(R.id.flutter_view);
+        File appBundle = new File(PathUtils.getDataDirectory(this), SkyApplication.APP_BUNDLE);
+        flutterView.runFromBundle(appBundle.getPath(), null);
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (flutterView != null) {
+            flutterView.destroy();
+        }
+        super.onDestroy();
+    }
+}
diff --git a/examples/hello_android/app/src/main/res/layout/flutter_layout.xml b/examples/hello_android/app/src/main/res/layout/flutter_layout.xml
new file mode 100644
index 0000000..bc8c11d
--- /dev/null
+++ b/examples/hello_android/app/src/main/res/layout/flutter_layout.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+  <TextView
+      android:id="@+id/text_view"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/title"
+      />
+  <org.domokit.sky.shell.PlatformViewAndroid
+      android:id="@+id/flutter_view"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent"
+      />
+</LinearLayout>
diff --git a/examples/hello_android/app/src/main/res/values/strings.xml b/examples/hello_android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..670cfae
--- /dev/null
+++ b/examples/hello_android/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Flutter App</string>
+    <string name="title">Flutter Application</string>
+</resources>
diff --git a/examples/hello_android/build.gradle b/examples/hello_android/build.gradle
new file mode 100644
index 0000000..ba21767
--- /dev/null
+++ b/examples/hello_android/build.gradle
@@ -0,0 +1,17 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.5.0'
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+task wrapper(type: Wrapper) {
+    gradleVersion = '2.8'
+}
diff --git a/examples/hello_android/buildSrc/build.gradle b/examples/hello_android/buildSrc/build.gradle
new file mode 100644
index 0000000..b62a213
--- /dev/null
+++ b/examples/hello_android/buildSrc/build.gradle
@@ -0,0 +1,7 @@
+repositories {
+  jcenter()
+}
+
+dependencies {
+  compile "com.android.tools.build:gradle:1.5.0"
+}
diff --git a/examples/hello_android/buildSrc/src/main/groovy/FlutterPlugin.groovy b/examples/hello_android/buildSrc/src/main/groovy/FlutterPlugin.groovy
new file mode 100644
index 0000000..bdd0c2e
--- /dev/null
+++ b/examples/hello_android/buildSrc/src/main/groovy/FlutterPlugin.groovy
@@ -0,0 +1,107 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.domokit.sky.gradle
+
+import com.android.builder.model.AndroidProject
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+class FlutterPlugin implements Plugin<Project> {
+    private File sdkDir
+
+    @Override
+    void apply(Project project) {
+        Properties properties = new Properties()
+        properties.load(project.rootProject.file("local.properties").newDataInputStream())
+
+        String enginePath = properties.getProperty("flutter.jar")
+        if (enginePath == null) {
+            throw new GradleException("flutter.jar must be defined in local.properties")
+        }
+        FileCollection flutterEngine = project.files(enginePath)
+        if (!flutterEngine.singleFile.isFile()) {
+            throw new GradleException("flutter.jar must point to a Flutter engine JAR")
+        }
+
+        String sdkPath = properties.getProperty("flutter.sdk")
+        if (sdkPath == null) {
+            throw new GradleException("flutter.sdk must be defined in local.properties")
+        }
+        sdkDir = project.file(sdkPath)
+        if (!sdkDir.isDirectory()) {
+            throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
+        }
+
+        project.extensions.create("flutter", FlutterExtension)
+        project.dependencies.add("compile", flutterEngine)
+        project.afterEvaluate this.&addFlutterTask
+    }
+
+    private void addFlutterTask(Project project) {
+        if (project.flutter.source == null) {
+            throw new GradleException("Must provide Flutter source directory")
+        }
+
+        FlutterTask flutterTask = project.tasks.create("flutterBuild", FlutterTask) {
+            sdkDir this.sdkDir
+            sourceDir project.file(project.flutter.source)
+            intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter")
+        }
+
+        project.android.applicationVariants.all { variant ->
+            Task copyFlxTask = project.tasks.create(name: "copyFlx${variant.name.capitalize()}", type: Copy) {
+                dependsOn flutterTask
+                dependsOn variant.mergeAssets
+                from flutterTask.flxPath
+                into variant.mergeAssets.outputDir
+            }
+            variant.outputs[0].processResources.dependsOn(copyFlxTask)
+        }
+    }
+}
+
+class FlutterExtension {
+    String source
+}
+
+class FlutterTask extends DefaultTask {
+    File sdkDir
+
+    @InputDirectory
+    File sourceDir
+
+    @OutputDirectory
+    File intermediateDir
+
+    String getFlxPath() {
+        return "${intermediateDir}/app.flx"
+    }
+
+    @TaskAction
+    void build() {
+        if (!sourceDir.isDirectory()) {
+            throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
+        }
+
+        intermediateDir.mkdirs()
+        project.exec {
+            executable "${sdkDir}/bin/flutter"
+            workingDir sourceDir
+            args "build"
+            args "-o", flxPath
+            args "--snapshot", "${intermediateDir}/snapshot_blob.bin"
+            args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
+            args "--working-dir", "${intermediateDir}/flx"
+        }
+    }
+}
diff --git a/examples/hello_android/buildSrc/src/main/resources/META-INF/gradle-plugins/flutter.properties b/examples/hello_android/buildSrc/src/main/resources/META-INF/gradle-plugins/flutter.properties
new file mode 100644
index 0000000..f8219be
--- /dev/null
+++ b/examples/hello_android/buildSrc/src/main/resources/META-INF/gradle-plugins/flutter.properties
@@ -0,0 +1 @@
+implementation-class=org.domokit.sky.gradle.FlutterPlugin
diff --git a/examples/hello_android/settings.gradle b/examples/hello_android/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/hello_android/settings.gradle
@@ -0,0 +1 @@
+include ':app'