Add module template for Android (#18697)
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 1cefa1f..833ae58 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -141,16 +141,13 @@ } } -String _locateProjectGradlew({ bool ensureExecutable = true }) { - final String path = fs.path.join( - 'android', +String _locateGradlewExecutable(Directory directory) { + final File gradle = directory.childFile( platform.isWindows ? 'gradlew.bat' : 'gradlew', ); - if (fs.isFileSync(path)) { - final File gradle = fs.file(path); - if (ensureExecutable) - os.makeExecutable(gradle); + if (gradle.existsSync()) { + os.makeExecutable(gradle); return gradle.absolute.path; } else { return null; @@ -165,11 +162,12 @@ // Note: Gradle may be bootstrapped and possibly downloaded as a side-effect // of validating the Gradle executable. This may take several seconds. Future<String> _initializeGradle() async { + final Directory android = fs.directory('android'); final Status status = logger.startProgress('Initializing gradle...', expectSlowOperation: true); - String gradle = _locateProjectGradlew(); + String gradle = _locateGradlewExecutable(android); if (gradle == null) { - _injectGradleWrapper(); - gradle = _locateProjectGradlew(); + injectGradleWrapper(android); + gradle = _locateGradlewExecutable(android); } if (gradle == null) throwToolExit('Unable to locate gradlew script'); @@ -181,11 +179,13 @@ return gradle; } -void _injectGradleWrapper() { - copyDirectorySync(cache.getArtifactDirectory('gradle_wrapper'), fs.directory('android')); - final String propertiesPath = fs.path.join('android', 'gradle', 'wrapper', 'gradle-wrapper.properties'); - if (!fs.file(propertiesPath).existsSync()) { - fs.file(propertiesPath).writeAsStringSync(''' +/// Injects the Gradle wrapper into the specified directory. +void injectGradleWrapper(Directory directory) { + copyDirectorySync(cache.getArtifactDirectory('gradle_wrapper'), directory); + _locateGradlewExecutable(directory); + final File propertiesFile = directory.childFile(fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties')); + if (!propertiesFile.existsSync()) { + propertiesFile.writeAsStringSync(''' distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME @@ -196,14 +196,31 @@ } } -/// Create android/local.properties if needed, and update Flutter settings. +/// Overwrite android/local.properties in the specified Flutter project, if needed. +/// +/// Throws, if `pubspec.yaml` or Android SDK cannot be located. Future<void> updateLocalProperties({String projectPath, BuildInfo buildInfo}) async { - final File localProperties = (projectPath == null) - ? fs.file(fs.path.join('android', 'local.properties')) - : fs.file(fs.path.join(projectPath, 'android', 'local.properties')); + final Directory android = (projectPath == null) + ? fs.directory('android') + : fs.directory(fs.path.join(projectPath, 'android')); final String flutterManifest = (projectPath == null) ? fs.path.join(bundle.defaultManifestPath) : fs.path.join(projectPath, bundle.defaultManifestPath); + if (androidSdk == null) { + throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.'); + } + FlutterManifest manifest; + try { + manifest = await FlutterManifest.createFromPath(flutterManifest); + } catch (error) { + throwToolExit('Failed to load pubspec.yaml: $error'); + } + updateLocalPropertiesSync(android, manifest, buildInfo); +} + +/// Overwrite local.properties in the specified directory, if needed. +void updateLocalPropertiesSync(Directory android, FlutterManifest manifest, [BuildInfo buildInfo]) { + final File localProperties = android.childFile('local.properties'); bool changed = false; SettingsFile settings; @@ -211,40 +228,27 @@ settings = new SettingsFile.parseFromFile(localProperties); } else { settings = new SettingsFile(); - if (androidSdk == null) { - throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.'); + changed = true; + } + + void changeIfNecessary(String key, String value) { + if (settings.values[key] != value) { + settings.values[key] = value; + changed = true; } - settings.values['sdk.dir'] = escapePath(androidSdk.directory); - changed = true; - } - final String escapedRoot = escapePath(Cache.flutterRoot); - if (changed || settings.values['flutter.sdk'] != escapedRoot) { - settings.values['flutter.sdk'] = escapedRoot; - changed = true; - } - if (buildInfo != null && settings.values['flutter.buildMode'] != buildInfo.modeName) { - settings.values['flutter.buildMode'] = buildInfo.modeName; - changed = true; } - FlutterManifest manifest; - try { - manifest = await FlutterManifest.createFromPath(flutterManifest); - } catch (error) { - throwToolExit('Failed to load pubspec.yaml: $error'); - } - + if (androidSdk != null) + changeIfNecessary('sdk.dir', escapePath(androidSdk.directory)); + changeIfNecessary('flutter.sdk', escapePath(Cache.flutterRoot)); + if (buildInfo != null) + changeIfNecessary('flutter.buildMode', buildInfo.modeName); final String buildName = buildInfo?.buildName ?? manifest.buildName; - if (buildName != null) { - settings.values['flutter.versionName'] = buildName; - changed = true; - } - + if (buildName != null) + changeIfNecessary('flutter.versionName', buildName); final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber; - if (buildNumber != null) { - settings.values['flutter.versionCode'] = '$buildNumber'; - changed = true; - } + if (buildNumber != null) + changeIfNecessary('flutter.versionCode', '$buildNumber'); if (changed) settings.writeContents(localProperties);
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 04e0000..3b3aefb 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -44,7 +44,7 @@ argParser.addOption( 'template', abbr: 't', - allowed: <String>['app', 'package', 'plugin'], + allowed: <String>['app', 'module', 'package', 'plugin'], help: 'Specify the type of project to create.', valueHelp: 'type', allowedHelp: <String, String>{ @@ -124,6 +124,7 @@ throwToolExit('Unable to find package:flutter_driver in $flutterDriverPackagePath', exitCode: 2); final String template = argResults['template']; + final bool generateModule = template == 'module'; final bool generatePlugin = template == 'plugin'; final bool generatePackage = template == 'package'; @@ -172,6 +173,9 @@ case 'app': generatedFileCount += await _generateApp(dirPath, templateContext); break; + case 'module': + generatedFileCount += await _generateModule(dirPath, templateContext); + break; case 'package': generatedFileCount += await _generatePackage(dirPath, templateContext); break; @@ -185,6 +189,9 @@ if (generatePackage) { final String relativePath = fs.path.relative(dirPath); printStatus('Your package code is in lib/${templateContext['projectName']}.dart in the $relativePath directory.'); + } else if (generateModule) { + final String relativePath = fs.path.relative(dirPath); + printStatus('Your module code is in lib/main.dart in the $relativePath directory.'); } else { // Run doctor; tell the user the next steps. final String relativeAppPath = fs.path.relative(appPath); @@ -226,6 +233,25 @@ } } + Future<int> _generateModule(String dirPath, Map<String, dynamic> templateContext) async { + int generatedCount = 0; + final String description = argResults.wasParsed('description') + ? argResults['description'] + : 'A new flutter module project.'; + templateContext['description'] = description; + generatedCount += _renderTemplate(fs.path.join('module', 'common'), dirPath, templateContext); + if (argResults['pub']) { + await pubGet( + context: PubContext.create, + directory: dirPath, + offline: argResults['offline'], + ); + final FlutterProject project = new FlutterProject(fs.directory(dirPath)); + await project.ensureReadyForPlatformSpecificTooling(); + } + return generatedCount; + } + Future<int> _generatePackage(String dirPath, Map<String, dynamic> templateContext) async { int generatedCount = 0; final String description = argResults.wasParsed('description')
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index c151839..80fef7a 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -87,6 +87,23 @@ return _flutterDescriptor['uses-material-design'] ?? false; } + /// Properties defining how to expose this Flutter project as a module + /// for integration into an unspecified host app. + Map<String, dynamic> get moduleDescriptor { + return _flutterDescriptor.containsKey('module') + ? _flutterDescriptor['module'] ?? const <String, dynamic>{} + : null; + } + + /// True if this manifest declares a Flutter module project. + /// + /// A Flutter project is considered a module when it has a `module:` + /// descriptor. A Flutter module project supports integration into an + /// existing host app. + /// + /// Such a project can be created using `flutter create -t module`. + bool get isModule => moduleDescriptor != null; + List<Map<String, dynamic>> get fontsDescriptor { return _flutterDescriptor['fonts'] ?? const <Map<String, dynamic>>[]; }
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 4dfd917..8099b89 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -51,6 +51,8 @@ /// /// targetOverride: Optional parameter, if null or unspecified the default value /// from xcode_backend.sh is used 'lib/main.dart'. +/// +/// Returns the number of files written. Future<void> updateGeneratedXcodeProperties({ @required String projectPath, @required BuildInfo buildInfo,
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 51a7f0d..f72463c 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -148,7 +148,7 @@ final String pluginRegistry = new mustache.Template(_androidPluginRegistryTemplate).renderString(context); - final String javaSourcePath = fs.path.join(directory, 'android', 'app', 'src', 'main', 'java'); + final String javaSourcePath = fs.path.join(directory, 'src', 'main', 'java'); final Directory registryDirectory = fs.directory(fs.path.join(javaSourcePath, 'io', 'flutter', 'plugins')); registryDirectory.createSync(recursive: true); @@ -233,9 +233,11 @@ directory ??= fs.currentDirectory.path; final List<Plugin> plugins = _findPlugins(directory); final bool changed = _writeFlutterPluginsList(directory, plugins); - - if (fs.isDirectorySync(fs.path.join(directory, 'android'))) - _writeAndroidPluginRegistrant(directory, plugins); + if (fs.isDirectorySync(fs.path.join(directory, '.android', 'Flutter'))) { + _writeAndroidPluginRegistrant(fs.path.join(directory, '.android', 'Flutter'), plugins); + } else if (fs.isDirectorySync(fs.path.join(directory, 'android', 'app'))) { + _writeAndroidPluginRegistrant(fs.path.join(directory, 'android', 'app'), plugins); + } if (fs.isDirectorySync(fs.path.join(directory, 'ios'))) { _writeIOSPluginRegistrant(directory, plugins); final CocoaPods cocoaPods = new CocoaPods();
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index f1d2b94..dae7772 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -5,10 +5,14 @@ import 'dart:async'; import 'dart:convert'; -import 'base/file_system.dart'; -import 'ios/xcodeproj.dart'; -import 'plugins.dart'; +import 'android/gradle.dart' as gradle; +import 'base/file_system.dart'; +import 'cache.dart'; +import 'flutter_manifest.dart'; +import 'ios/xcodeproj.dart' as xcode; +import 'plugins.dart'; +import 'template.dart'; /// Represents the contents of a Flutter project at the specified [directory]. class FlutterProject { @@ -47,6 +51,9 @@ /// The Android sub project of this project. AndroidProject get android => new AndroidProject(directory.childDirectory('android')); + /// The generated AndroidModule sub project of this module project. + AndroidModuleProject get androidModule => new AndroidModuleProject(directory.childDirectory('.android')); + /// Returns true if this project has an example application bool get hasExampleApp => directory.childDirectory('example').childFile('pubspec.yaml').existsSync(); @@ -54,13 +61,19 @@ FlutterProject get example => new FlutterProject(directory.childDirectory('example')); /// Generates project files necessary to make Gradle builds work on Android - /// and CocoaPods+Xcode work on iOS, for app projects only + /// and CocoaPods+Xcode work on iOS, for app and module projects only. + /// + /// Returns the number of files written. Future<void> ensureReadyForPlatformSpecificTooling() async { if (!directory.existsSync() || hasExampleApp) { - return; + return 0; + } + final FlutterManifest manifest = await FlutterManifest.createFromPath(directory.childFile('pubspec.yaml').path); + if (manifest.isModule) { + await androidModule.ensureReadyForPlatformSpecificTooling(manifest); } injectPlugins(directory: directory.path); - await generateXcodeProperties(directory.path); + await xcode.generateXcodeProperties(directory.path); } } @@ -97,6 +110,36 @@ } } +/// Represents the contents of the .android-generated/ folder of a Flutter module +/// project. +class AndroidModuleProject { + AndroidModuleProject(this.directory); + + final Directory directory; + + Future<void> ensureReadyForPlatformSpecificTooling(FlutterManifest manifest) async { + if (_shouldRegenerate()) { + final Template template = new Template.fromName(fs.path.join('module', 'android')); + template.render(directory, <String, dynamic>{ + 'androidIdentifier': manifest.moduleDescriptor['androidPackage'], + }, printStatusWhenWriting: false); + gradle.injectGradleWrapper(directory); + } + gradle.updateLocalPropertiesSync(directory, manifest); + } + + bool _shouldRegenerate() { + final File flutterToolsStamp = Cache.instance.getStampFileFor('flutter_tools'); + final File buildDotGradleFile = directory.childFile('build.gradle'); + if (!buildDotGradleFile.existsSync()) + return true; + return flutterToolsStamp.existsSync() && + flutterToolsStamp + .lastModifiedSync() + .isAfter(buildDotGradleFile.lastModifiedSync()); + } +} + /// Asynchronously returns the first line-based match for [regExp] in [file]. /// /// Assumes UTF8 encoding.
diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index 71c47ba..3f22211 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart
@@ -66,6 +66,7 @@ Directory destination, Map<String, dynamic> context, { bool overwriteExisting = true, + bool printStatusWhenWriting = true, }) { destination.createSync(recursive: true); int fileCount = 0; @@ -117,14 +118,17 @@ if (finalDestinationFile.existsSync()) { if (overwriteExisting) { finalDestinationFile.deleteSync(recursive: true); - printStatus(' $relativePathForLogging (overwritten)'); + if (printStatusWhenWriting) + printStatus(' $relativePathForLogging (overwritten)'); } else { // The file exists but we cannot overwrite it, move on. - printTrace(' $relativePathForLogging (existing - skipped)'); + if (printStatusWhenWriting) + printTrace(' $relativePathForLogging (existing - skipped)'); return; } } else { - printStatus(' $relativePathForLogging (created)'); + if (printStatusWhenWriting) + printStatus(' $relativePathForLogging (created)'); } fileCount++;
diff --git a/packages/flutter_tools/schema/pubspec_yaml.json b/packages/flutter_tools/schema/pubspec_yaml.json index 0045bf4..deafe91 100644 --- a/packages/flutter_tools/schema/pubspec_yaml.json +++ b/packages/flutter_tools/schema/pubspec_yaml.json
@@ -43,6 +43,13 @@ } } }, + "module": { + "type": "object", + "additionalProperties": false, + "properties": { + "androidPackage": { "type": "string" } + } + }, "plugin": { "type": "object", "additionalProperties": false,
diff --git a/packages/flutter_tools/templates/module/android/Flutter.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/Flutter.tmpl/build.gradle.tmpl new file mode 100644 index 0000000..c156a87 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/Flutter.tmpl/build.gradle.tmpl
@@ -0,0 +1,48 @@ +// Generated file. Do not edit. + +def localProperties = new Properties() +def localPropertiesFile = new File(buildscript.sourceFile.parentFile.parentFile, 'local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.") +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.") +} + +apply plugin: 'com.android.library' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 27 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:support-v13:27.1.1' +}
diff --git a/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl new file mode 100644 index 0000000..7466643 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl
@@ -0,0 +1,5 @@ +<!-- Generated file. Do not edit. --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="{{androidIdentifier}}"> + <uses-permission android:name="android.permission.INTERNET"/> +</manifest>
diff --git a/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/java/io/flutter/facade/Flutter.java b/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/java/io/flutter/facade/Flutter.java new file mode 100644 index 0000000..a52fd9f --- /dev/null +++ b/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/java/io/flutter/facade/Flutter.java
@@ -0,0 +1,98 @@ +package io.flutter.facade; + +import android.app.Activity; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleObserver; +import android.arch.lifecycle.OnLifecycleEvent; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.View; + +import io.flutter.app.FlutterActivityDelegate; +import io.flutter.view.FlutterMain; +import io.flutter.view.FlutterNativeView; +import io.flutter.view.FlutterView; +import io.flutter.plugins.GeneratedPluginRegistrant; + +/** + * Main entry point for using Flutter in Android applications. + * + * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling. Do not edit. + * It may be moved into flutter.jar or another library dependency of the Flutter module project + * at a later point.</p> + */ +public final class Flutter { + private Flutter() { + // to prevent instantiation + } + + public static void startInitialization(Context applicationContext) { + FlutterMain.startInitialization(applicationContext, null); + } + + public static Fragment createFragment(String route) { + final FlutterFragment fragment = new FlutterFragment(); + final Bundle args = new Bundle(); + args.putString(FlutterFragment.ARG_ROUTE, route); + fragment.setArguments(args); + return fragment; + } + + public static View createView(final Activity activity, final Lifecycle lifecycle, final String route) { + FlutterMain.startInitialization(activity.getApplicationContext()); + FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), null); + final FlutterActivityDelegate delegate = new FlutterActivityDelegate(activity, new FlutterActivityDelegate.ViewFactory() { + @Override + public FlutterView createFlutterView(Context context) { + final FlutterNativeView nativeView = new FlutterNativeView(context); + final FlutterView flutterView = new FlutterView(activity, null, nativeView); + flutterView.setInitialRoute(route); + return flutterView; + } + + @Override + public boolean retainFlutterNativeView() { + return false; + } + + @Override + public FlutterNativeView createFlutterNativeView() { + throw new UnsupportedOperationException(); + } + }); + lifecycle.addObserver(new LifecycleObserver() { + @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) + public void onCreate() { + delegate.onCreate(null); + GeneratedPluginRegistrant.registerWith(delegate); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + public void onStart() { + delegate.onStart(); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + public void onResume() { + delegate.onResume(); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + public void onPause() { + delegate.onPause(); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + public void onStop() { + delegate.onStop(); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + public void onDestroy() { + delegate.onDestroy(); + } + }); + return delegate.getFlutterView(); + } +}
diff --git a/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/java/io/flutter/facade/FlutterFragment.java b/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/java/io/flutter/facade/FlutterFragment.java new file mode 100644 index 0000000..1b082de --- /dev/null +++ b/packages/flutter_tools/templates/module/android/Flutter.tmpl/src/main/java/io/flutter/facade/FlutterFragment.java
@@ -0,0 +1,40 @@ +package io.flutter.facade; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link Fragment} managing a Flutter view. + * + * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling. Do not edit. + * It may be moved into flutter.jar or another library dependency of the Flutter module project + * at a later point.</p> + */ +public class FlutterFragment extends Fragment { + public static final String ARG_ROUTE = "route"; + private String mRoute = "/"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mRoute = getArguments().getString(ARG_ROUTE); + } + } + + @Override + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(context, attrs, savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return Flutter.createView(getActivity(), getLifecycle(), mRoute); + } +}
diff --git a/packages/flutter_tools/templates/module/android/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/build.gradle.tmpl new file mode 100644 index 0000000..fdb7698 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/build.gradle.tmpl
@@ -0,0 +1,31 @@ +// Generated file. Do not edit. + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.1.3' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/android_gen" +} +subprojects { + project.evaluationDependsOn(':flutter') +} + +task clean(type: Delete) { + delete rootProject.buildDir +}
diff --git a/packages/flutter_tools/templates/module/android/gradle.properties.tmpl b/packages/flutter_tools/templates/module/android/gradle.properties.tmpl new file mode 100644 index 0000000..8bd86f6 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/gradle.properties.tmpl
@@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M
diff --git a/packages/flutter_tools/templates/module/android/gradle.tmpl/wrapper/gradle-wrapper.properties b/packages/flutter_tools/templates/module/android/gradle.tmpl/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9372d0f --- /dev/null +++ b/packages/flutter_tools/templates/module/android/gradle.tmpl/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/packages/flutter_tools/templates/module/android/include_flutter.groovy.tmpl b/packages/flutter_tools/templates/module/android/include_flutter.groovy.tmpl new file mode 100644 index 0000000..e3a0dea --- /dev/null +++ b/packages/flutter_tools/templates/module/android/include_flutter.groovy.tmpl
@@ -0,0 +1,19 @@ +// Generated file. Do not edit. + +def scriptFile = getClass().protectionDomain.codeSource.location.path +def flutterProjectRoot = new File(scriptFile).parentFile.parentFile + +gradle.include ':flutter' +gradle.project(':flutter').projectDir = new File(flutterProjectRoot, '.android/Flutter') + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile() + gradle.include ":$name" + gradle.project(":$name").projectDir = pluginDirectory +}
diff --git a/packages/flutter_tools/templates/module/android/settings.gradle.tmpl b/packages/flutter_tools/templates/module/android/settings.gradle.tmpl new file mode 100644 index 0000000..49eb997 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/settings.gradle.tmpl
@@ -0,0 +1,5 @@ +// Generated file. Do not edit. + +rootProject.name = 'android_generated' +setBinding(new Binding([gradle: this])) +evaluate(new File('include_flutter.groovy'))
diff --git a/packages/flutter_tools/templates/module/common/.gitignore.tmpl b/packages/flutter_tools/templates/module/common/.gitignore.tmpl new file mode 100644 index 0000000..c15d674 --- /dev/null +++ b/packages/flutter_tools/templates/module/common/.gitignore.tmpl
@@ -0,0 +1,40 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +android_gen/ +.flutter-plugins
diff --git a/packages/flutter_tools/templates/module/common/.metadata.tmpl b/packages/flutter_tools/templates/module/common/.metadata.tmpl new file mode 100644 index 0000000..accfecc --- /dev/null +++ b/packages/flutter_tools/templates/module/common/.metadata.tmpl
@@ -0,0 +1,8 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: {{flutterRevision}} + channel: {{flutterChannel}}
diff --git a/packages/flutter_tools/templates/module/common/README.md.tmpl b/packages/flutter_tools/templates/module/common/README.md.tmpl new file mode 100644 index 0000000..f140e12 --- /dev/null +++ b/packages/flutter_tools/templates/module/common/README.md.tmpl
@@ -0,0 +1,8 @@ +# {{projectName}} + +{{description}} + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.io/).
diff --git a/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl b/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl new file mode 100644 index 0000000..1b325f6 --- /dev/null +++ b/packages/flutter_tools/templates/module/common/lib/main.dart.tmpl
@@ -0,0 +1,38 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:package_info/package_info.dart'; + +Future<void> main() async { + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + runApp(Directionality(textDirection: TextDirection.ltr, child: Router(packageInfo))); +} + +class Router extends StatelessWidget { + Router(this.packageInfo); + + final PackageInfo packageInfo; + + @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, + ); + } + } +}
diff --git a/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl b/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl new file mode 100644 index 0000000..dc6bb57 --- /dev/null +++ b/packages/flutter_tools/templates/module/common/pubspec.yaml.tmpl
@@ -0,0 +1,17 @@ +name: {{projectName}} +description: {{description}} +version: 1.0.0+1 + +dependencies: + package_info: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + module: + androidPackage: {{androidIdentifier}}
diff --git a/packages/flutter_tools/test/commands/create_test.dart b/packages/flutter_tools/test/commands/create_test.dart index 4666080..ad27491 100644 --- a/packages/flutter_tools/test/commands/create_test.dart +++ b/packages/flutter_tools/test/commands/create_test.dart
@@ -186,6 +186,59 @@ ); }, timeout: allowForRemotePubInvocation); + testUsingContext('module', () async { + return _createProject( + projectDir, + <String>['--no-pub', '--template=module'], + <String>[ + '.gitignore', + '.metadata', + 'lib/main.dart', + 'pubspec.yaml', + 'README.md', + ], + unexpectedPaths: <String>[ + '.android/', + 'android/', + 'ios/', + ] + ); + }, timeout: allowForCreateFlutterProject); + + testUsingContext('module with pub', () async { + return _createProject( + projectDir, + <String>['-t', 'module'], + <String>[ + '.gitignore', + '.metadata', + 'lib/main.dart', + 'pubspec.lock', + 'pubspec.yaml', + 'README.md', + '.packages', + '.android/build.gradle', + '.android/Flutter/build.gradle', + '.android/Flutter/src/main/java/io/flutter/facade/Flutter.java', + '.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java', + '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', + '.android/Flutter/src/main/AndroidManifest.xml', + '.android/gradle.properties', + '.android/gradle/wrapper/gradle-wrapper.jar', + '.android/gradle/wrapper/gradle-wrapper.properties', + '.android/gradlew', + '.android/gradlew.bat', + '.android/local.properties', + '.android/include_flutter.groovy', + '.android/settings.gradle', + ], + unexpectedPaths: <String>[ + 'android/', + 'ios/', + ] + ); + }, timeout: allowForRemotePubInvocation); + // Verify content and formatting testUsingContext('content', () async { Cache.flutterRoot = '../..'; @@ -423,11 +476,16 @@ args.add(dir.path); await runner.run(args); + bool pathExists(String path) { + final String fullPath = fs.path.join(dir.path, path); + return fs.typeSync(fullPath) != FileSystemEntityType.notFound; + } + for (String path in expectedPaths) { - expect(fs.file(fs.path.join(dir.path, path)).existsSync(), true, reason: '$path does not exist'); + expect(pathExists(path), true, reason: '$path does not exist'); } for (String path in unexpectedPaths) { - expect(fs.file(fs.path.join(dir.path, path)).existsSync(), false, reason: '$path exists'); + expect(pathExists(path), false, reason: '$path exists'); } }