From d1f446e559dc37671ef8bb8a8a1cc76c0b379821 Mon Sep 17 00:00:00 2001 From: Mikkel Nygaard Ravn Date: Sat, 4 Aug 2018 13:52:09 +0200 Subject: [PATCH] Support flutter run/build of module on Android (#20197) --- dev/devicelab/bin/tasks/module_test.dart | 32 ++- .../flutter_tools/lib/src/android/gradle.dart | 5 +- packages/flutter_tools/lib/src/project.dart | 7 +- .../module/android/app.tmpl/build.gradle.tmpl | 35 ++++ .../src/main/AndroidManifest.xml.tmpl | 39 ++++ .../host/MainActivity.java.tmpl | 13 ++ .../main/res/drawable/launch_background.xml | 12 ++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../app.tmpl/src/main/res/values/styles.xml | 8 + .../module/android/settings.gradle.tmpl | 1 + .../module/common/lib/main.dart.tmpl | 198 +++++++++++++++--- .../templates/module/common/pubspec.yaml.tmpl | 1 - 12 files changed, 315 insertions(+), 36 deletions(-) create mode 100644 packages/flutter_tools/templates/module/android/app.tmpl/build.gradle.tmpl create mode 100644 packages/flutter_tools/templates/module/android/app.tmpl/src/main/AndroidManifest.xml.tmpl create mode 100644 packages/flutter_tools/templates/module/android/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl create mode 100644 packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/drawable/launch_background.xml create mode 100644 packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/values/styles.xml diff --git a/dev/devicelab/bin/tasks/module_test.dart b/dev/devicelab/bin/tasks/module_test.dart index c1d0fd2c9f7..696d7adddd0 100644 --- a/dev/devicelab/bin/tasks/module_test.dart +++ b/dev/devicelab/bin/tasks/module_test.dart @@ -31,7 +31,7 @@ Future main() async { ); }); - section('Build Android .aar'); + section('Build Flutter module library archive'); await inDirectory(new Directory(path.join(directory.path, 'hello', '.android')), () async { await exec( @@ -56,6 +56,30 @@ Future main() async { return new TaskResult.failure('Failed to build .aar'); } + section('Build ephemeral host app'); + + await inDirectory(new Directory(path.join(directory.path, 'hello')), () async { + await flutter( + 'build', + options: ['apk'], + ); + }); + + final bool apkBuilt = exists(new File(path.join( + directory.path, + 'hello', + 'build', + 'host', + 'outputs', + 'apk', + 'release', + 'app-release.apk', + ))); + + if (!apkBuilt) { + return new TaskResult.failure('Failed to build ephemeral host .apk'); + } + section('Add to Android app'); final Directory hostApp = new Directory(path.join(directory.path, 'hello_host_app')); @@ -81,7 +105,7 @@ Future main() async { ); }); - final bool appBuilt = exists(new File(path.join( + final bool existingAppBuilt = exists(new File(path.join( hostApp.path, 'app', 'build', @@ -91,8 +115,8 @@ Future main() async { 'app-debug.apk', ))); - if (!appBuilt) { - return new TaskResult.failure('Failed to build .apk'); + if (!existingAppBuilt) { + return new TaskResult.failure('Failed to build existing app .apk'); } return new TaskResult.success(null); } catch (e) { diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 8842503497a..2357f2219e5 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -43,8 +43,6 @@ final RegExp ndkMessageFilter = new RegExp(r'^(?!NDK is missing a ".*" directory r'|If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning' r'|If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to .*)'); - - FlutterPluginVersion getFlutterPluginVersion(AndroidProject project) { final File plugin = project.directory.childFile( fs.path.join('buildSrc', 'src', 'main', 'groovy', 'FlutterPlugin.groovy')); @@ -62,6 +60,9 @@ FlutterPluginVersion getFlutterPluginVersion(AndroidProject project) { if (line.contains(new RegExp(r'apply from: .*/flutter.gradle'))) { return FlutterPluginVersion.managed; } + if (line.contains("def flutterPluginVersion = 'managed'")) { + return FlutterPluginVersion.managed; + } } } return FlutterPluginVersion.none; diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 41b15927434..196924c13af 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -76,7 +76,12 @@ class FlutterProject { IosProject get ios => new IosProject(directory.childDirectory('ios')); /// The Android sub project of this project. - AndroidProject get android => new AndroidProject(directory.childDirectory('android')); + AndroidProject get android { + if (manifest.isModule) { + return new AndroidProject(directory.childDirectory('.android')); + } + return new AndroidProject(directory.childDirectory('android')); + } /// The generated AndroidModule sub project of this module project. AndroidModuleProject get androidModule => new AndroidModuleProject(directory.childDirectory('.android')); diff --git a/packages/flutter_tools/templates/module/android/app.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/app.tmpl/build.gradle.tmpl new file mode 100644 index 00000000000..3eb860c4cdd --- /dev/null +++ b/packages/flutter_tools/templates/module/android/app.tmpl/build.gradle.tmpl @@ -0,0 +1,35 @@ +def flutterPluginVersion = 'managed' + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "{{androidIdentifier}}.host" + minSdkVersion 16 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + +} +buildDir = new File(rootProject.projectDir, "../build/host") +dependencies { + implementation project(':flutter') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support:design:27.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/packages/flutter_tools/templates/module/android/app.tmpl/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/AndroidManifest.xml.tmpl new file mode 100644 index 00000000000..e9f4d8ed2d8 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/AndroidManifest.xml.tmpl @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_tools/templates/module/android/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl new file mode 100644 index 00000000000..3441c3ad9a5 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl @@ -0,0 +1,13 @@ +package {{androidIdentifier}}.host; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/drawable/launch_background.xml b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000000..304732f8842 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/values/styles.xml b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/values/styles.xml new file mode 100644 index 00000000000..00fa4417cfb --- /dev/null +++ b/packages/flutter_tools/templates/module/android/app.tmpl/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/packages/flutter_tools/templates/module/android/settings.gradle.tmpl b/packages/flutter_tools/templates/module/android/settings.gradle.tmpl index 49eb9974499..0605586ecbc 100644 --- a/packages/flutter_tools/templates/module/android/settings.gradle.tmpl +++ b/packages/flutter_tools/templates/module/android/settings.gradle.tmpl @@ -1,4 +1,5 @@ // Generated file. Do not edit. +include ':app' rootProject.name = 'android_generated' setBinding(new Binding([gradle: this])) diff --git a/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl b/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl index 1b325f60c0c..ce3660df8dc 100644 --- a/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl +++ b/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl @@ -1,38 +1,180 @@ -import 'dart:async'; -import 'dart:ui'; - import 'package:flutter/material.dart'; -import 'package:package_info/package_info.dart'; +{{#withDriverTest}} +import 'package:flutter_driver/driver_extension.dart'; +{{/withDriverTest}} +{{#withPluginHook}} +import 'dart:async'; -Future main() async { - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - runApp(Directionality(textDirection: TextDirection.ltr, child: Router(packageInfo))); +import 'package:flutter/services.dart'; +import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart'; +{{/withPluginHook}} + +{{^withDriverTest}} +void main() => runApp(new MyApp()); +{{/withDriverTest}} +{{#withDriverTest}} +void main() { + // Enable integration testing with the Flutter Driver extension. + // See https://flutter.io/testing/ for more info. + enableFlutterDriverExtension(); + runApp(new MyApp()); +} +{{/withDriverTest}} + +{{^withPluginHook}} +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return new MaterialApp( + title: 'Flutter Demo', + theme: new ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or press Run > Flutter Hot Reload in IntelliJ). Notice that the + // counter didn't reset back to zero; the application is not restarted. + primarySwatch: Colors.blue, + ), + home: new MyHomePage(title: 'Flutter Demo Home Page'), + ); + } } -class Router extends StatelessWidget { - Router(this.packageInfo); +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); - final PackageInfo packageInfo; + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + _MyHomePageState createState() => new _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } @override Widget build(BuildContext context) { - final String route = window.defaultRouteName; - switch (route) { - case 'route1': - return Container( - child: Center(child: Text('Route 1\n${packageInfo.appName}')), - color: Colors.green, - ); - case 'route2': - return Container( - child: Center(child: Text('Route 2\n${packageInfo.packageName}')), - color: Colors.blue, - ); - default: - return Container( - child: Center(child: Text('Unknown route: $route')), - color: Colors.red, - ); - } + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return new Scaffold( + appBar: new AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: new Text(widget.title), + ), + body: new Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: new Column( + // Column is also layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug paint" (press "p" in the console where you ran + // "flutter run", or select "Toggle Debug Paint" from the Flutter tool + // window in IntelliJ) to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Text( + 'You have pushed the button this many times:', + ), + new Text( + '$_counter', + style: Theme.of(context).textTheme.display1, + ), + ], + ), + ), + floatingActionButton: new FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: new Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); } } +{{/withPluginHook}} +{{#withPluginHook}} +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => new _MyAppState(); +} + +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String platformVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + try { + platformVersion = await {{pluginDartClass}}.platformVersion; + } on PlatformException { + platformVersion = 'Failed to get platform version.'; + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _platformVersion = platformVersion; + }); + } + + @override + Widget build(BuildContext context) { + return new MaterialApp( + home: new Scaffold( + appBar: new AppBar( + title: const Text('Plugin example app'), + ), + body: new Center( + child: new Text('Running on: $_platformVersion\n'), + ), + ), + ); + } +} +{{/withPluginHook}} diff --git a/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl b/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl index dc6bb57fb9e..7a8edcf344c 100644 --- a/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl @@ -3,7 +3,6 @@ description: {{description}} version: 1.0.0+1 dependencies: - package_info: flutter: sdk: flutter