Commit d0e45a23 authored by Josh Burton's avatar Josh Burton Committed by Emmanuel Garcia

Adds support for generating projects that use AndroidX support libraries (#31028)

parent 16a23dd2
...@@ -137,6 +137,12 @@ class CreateCommand extends FlutterCommand { ...@@ -137,6 +137,12 @@ class CreateCommand extends FlutterCommand {
defaultsTo: 'java', defaultsTo: 'java',
allowed: <String>['java', 'kotlin'], allowed: <String>['java', 'kotlin'],
); );
argParser.addFlag(
'androidx',
negatable: true,
defaultsTo: false,
help: 'Generate a project using the AndroidX support libraries',
);
} }
@override @override
...@@ -358,6 +364,7 @@ class CreateCommand extends FlutterCommand { ...@@ -358,6 +364,7 @@ class CreateCommand extends FlutterCommand {
flutterRoot: flutterRoot, flutterRoot: flutterRoot,
renderDriverTest: argResults['with-driver-test'], renderDriverTest: argResults['with-driver-test'],
withPluginHook: generatePlugin, withPluginHook: generatePlugin,
androidX: argResults['androidx'],
androidLanguage: argResults['android-language'], androidLanguage: argResults['android-language'],
iosLanguage: argResults['ios-language'], iosLanguage: argResults['ios-language'],
); );
...@@ -564,6 +571,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -564,6 +571,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
String projectName, String projectName,
String projectDescription, String projectDescription,
String androidLanguage, String androidLanguage,
bool androidX,
String iosLanguage, String iosLanguage,
String flutterRoot, String flutterRoot,
bool renderDriverTest = false, bool renderDriverTest = false,
...@@ -583,6 +591,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -583,6 +591,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'iosIdentifier': _createUTIIdentifier(organization, projectName), 'iosIdentifier': _createUTIIdentifier(organization, projectName),
'description': projectDescription, 'description': projectDescription,
'dartSdk': '$flutterRoot/bin/cache/dart-sdk', 'dartSdk': '$flutterRoot/bin/cache/dart-sdk',
'androidX': androidX,
'androidMinApiLevel': android.minApiLevel, 'androidMinApiLevel': android.minApiLevel,
'androidSdkVersion': android_sdk.minimumAndroidSdkVersion, 'androidSdkVersion': android_sdk.minimumAndroidSdkVersion,
'androidFlutterJar': '$flutterRoot/bin/cache/artifacts/engine/android-arm/flutter.jar', 'androidFlutterJar': '$flutterRoot/bin/cache/artifacts/engine/android-arm/flutter.jar',
......
...@@ -119,6 +119,13 @@ class FlutterManifest { ...@@ -119,6 +119,13 @@ class FlutterManifest {
return _flutterDescriptor['uses-material-design'] ?? false; return _flutterDescriptor['uses-material-design'] ?? false;
} }
/// True if this Flutter module should use AndroidX dependencies.
///
/// If false the deprecated Android Support library will be used.
bool get usesAndroidX {
return _flutterDescriptor['module']['androidX'] ?? false;
}
/// True if this manifest declares a Flutter module project. /// True if this manifest declares a Flutter module project.
/// ///
/// A Flutter project is considered a module when it has a `module:` /// A Flutter project is considered a module when it has a `module:`
...@@ -356,6 +363,9 @@ void _validateFlutter(YamlMap yaml, List<String> errors) { ...@@ -356,6 +363,9 @@ void _validateFlutter(YamlMap yaml, List<String> errors) {
errors.add('Expected "${kvp.key}" to be an object, but got ${kvp.value} (${kvp.value.runtimeType}).'); errors.add('Expected "${kvp.key}" to be an object, but got ${kvp.value} (${kvp.value.runtimeType}).');
} }
if (kvp.value['androidX'] != null && kvp.value['androidX'] is! bool) {
errors.add('The "androidX" value must be a bool if set.');
}
if (kvp.value['androidPackage'] != null && kvp.value['androidPackage'] is! String) { if (kvp.value['androidPackage'] != null && kvp.value['androidPackage'] is! String) {
errors.add('The "androidPackage" value must be a string if set.'); errors.add('The "androidPackage" value must be a string if set.');
} }
......
...@@ -149,6 +149,9 @@ class FlutterProject { ...@@ -149,6 +149,9 @@ class FlutterProject {
/// True if this project is a Flutter module project. /// True if this project is a Flutter module project.
bool get isModule => manifest.isModule; bool get isModule => manifest.isModule;
/// True if the Flutter project is using the AndroidX support library
bool get usesAndroidX => manifest.usesAndroidX;
/// True if this project has an example application. /// True if this project has an example application.
bool get hasExampleApp => _exampleDirectory(directory).existsSync(); bool get hasExampleApp => _exampleDirectory(directory).existsSync();
...@@ -466,6 +469,9 @@ class AndroidProject { ...@@ -466,6 +469,9 @@ class AndroidProject {
/// True if the parent Flutter project is a module. /// True if the parent Flutter project is a module.
bool get isModule => parent.isModule; bool get isModule => parent.isModule;
/// True if the Flutter project is using the AndroidX support library
bool get usesAndroidX => parent.usesAndroidX;
/// True, if the app project is using Kotlin. /// True, if the app project is using Kotlin.
bool get isKotlin { bool get isKotlin {
final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle'); final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
...@@ -558,6 +564,7 @@ class AndroidProject { ...@@ -558,6 +564,7 @@ class AndroidProject {
<String, dynamic>{ <String, dynamic>{
'projectName': parent.manifest.appName, 'projectName': parent.manifest.appName,
'androidIdentifier': parent.manifest.androidPackage, 'androidIdentifier': parent.manifest.androidPackage,
'androidX': usesAndroidX,
}, },
printStatusWhenWriting: false, printStatusWhenWriting: false,
overwriteExisting: true, overwriteExisting: true,
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"androidX": { "type": "boolean" },
"androidPackage": { "type": "string" }, "androidPackage": { "type": "string" },
"iosBundleIdentifier": { "type": "string" } "iosBundleIdentifier": { "type": "string" }
} }
......
...@@ -38,7 +38,12 @@ android { ...@@ -38,7 +38,12 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
{{#androidX}}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
{{/androidX}}
{{^androidX}}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
{{/androidX}}
} }
buildTypes { buildTypes {
...@@ -56,6 +61,12 @@ flutter { ...@@ -56,6 +61,12 @@ flutter {
dependencies { dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
{{#androidX}}
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
{{/androidX}}
{{^androidX}}
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
{{/androidX}}
} }
...@@ -43,7 +43,12 @@ android { ...@@ -43,7 +43,12 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
{{#androidX}}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
{{/androidX}}
{{^androidX}}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
{{/androidX}}
} }
buildTypes { buildTypes {
...@@ -62,6 +67,12 @@ flutter { ...@@ -62,6 +67,12 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
{{#androidX}}
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
{{/androidX}}
{{^androidX}}
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
{{/androidX}}
} }
org.gradle.jvmargs=-Xmx1536M
{{#androidX}}
android.useAndroidX=true
android.enableJetifier=true
{{/androidX}}
org.gradle.jvmargs=-Xmx1536M
{{#androidX}}
android.useAndroidX=true
android.enableJetifier=true
{{/androidX}}
...@@ -16,7 +16,12 @@ android { ...@@ -16,7 +16,12 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
{{#androidX}}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
{{/androidX}}
{{^androidX}}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
{{/androidX}}
} }
buildTypes { buildTypes {
...@@ -35,10 +40,18 @@ buildDir = new File(rootProject.projectDir, "../build/host") ...@@ -35,10 +40,18 @@ buildDir = new File(rootProject.projectDir, "../build/host")
dependencies { dependencies {
implementation project(':flutter') implementation project(':flutter')
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
{{#androidX}}
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
{{/androidX}}
{{^androidX}}
implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2' implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:design:27.1.1' 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:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
{{/androidX}}
testImplementation 'junit:junit:4.12'
} }
...@@ -34,7 +34,12 @@ android { ...@@ -34,7 +34,12 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
{{#androidX}}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
{{/androidX}}
{{^androidX}}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
{{/androidX}}
} }
} }
...@@ -44,6 +49,11 @@ flutter { ...@@ -44,6 +49,11 @@ flutter {
dependencies { dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
{{#androidX}}
implementation 'androidx.appcompat:appcompat:1.0.2'
{{/androidX}}
{{^androidX}}
implementation 'com.android.support:support-v13:27.1.1' implementation 'com.android.support:support-v13:27.1.1'
implementation 'com.android.support:support-annotations:27.1.1' implementation 'com.android.support:support-annotations:27.1.1'
{{/androidX}}
} }
package io.flutter.facade; package io.flutter.facade;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
{{#androidX}}
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
{{/androidX}}
{{^androidX}}
import android.arch.lifecycle.Lifecycle; import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleObserver; import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.OnLifecycleEvent; import android.arch.lifecycle.OnLifecycleEvent;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
{{/androidX}}
import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.StringCodec; import io.flutter.plugin.common.StringCodec;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.view.FlutterMain; import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterNativeView; import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterRunArguments; import io.flutter.view.FlutterRunArguments;
import io.flutter.view.FlutterView; import io.flutter.view.FlutterView;
import io.flutter.plugins.GeneratedPluginRegistrant;
/** /**
* Main entry point for using Flutter in Android applications. * Main entry point for using Flutter in Android applications.
......
...@@ -2,12 +2,18 @@ package io.flutter.facade; ...@@ -2,12 +2,18 @@ package io.flutter.facade;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
{{#androidX}}
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
{{/androidX}}
{{^androidX}}
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
{{/androidX}}
import io.flutter.view.FlutterView; import io.flutter.view.FlutterView;
/** /**
......
...@@ -26,7 +26,12 @@ android { ...@@ -26,7 +26,12 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
{{#androidX}}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
{{/androidX}}
{{^androidX}}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
{{/androidX}}
} }
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
......
...@@ -32,7 +32,12 @@ android { ...@@ -32,7 +32,12 @@ android {
} }
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
{{#androidX}}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
{{/androidX}}
{{^androidX}}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
{{/androidX}}
} }
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
......
org.gradle.jvmargs=-Xmx1536M
{{#androidX}}
android.useAndroidX=true
android.enableJetifier=true
{{/androidX}}
...@@ -378,6 +378,91 @@ void main() { ...@@ -378,6 +378,91 @@ void main() {
]); ]);
}, timeout: allowForRemotePubInvocation); }, timeout: allowForRemotePubInvocation);
testUsingContext('androidx app project', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--androidx', projectDir.path]);
void expectExists(String relPath) {
expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
}
expectExists('android/gradle.properties');
final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
expect(actualContents.contains('useAndroidX'), true);
}, timeout: allowForCreateFlutterProject);
testUsingContext('non androidx app project', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--no-androidx', projectDir.path]);
void expectExists(String relPath) {
expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
}
expectExists('android/gradle.properties');
final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
expect(actualContents.contains('useAndroidX'), false);
}, timeout: allowForCreateFlutterProject);
testUsingContext('androidx plugin project', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--androidx', projectDir.path]);
void expectExists(String relPath) {
expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
}
expectExists('android/gradle.properties');
final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
expect(actualContents.contains('useAndroidX'), true);
}, timeout: allowForCreateFlutterProject);
testUsingContext('non androidx plugin project', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--no-androidx', projectDir.path]);
void expectExists(String relPath) {
expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
}
expectExists('android/gradle.properties');
final String actualContents = await fs.file(projectDir.path + '/android/gradle.properties').readAsString();
expect(actualContents.contains('useAndroidX'), false);
}, timeout: allowForCreateFlutterProject);
testUsingContext('has correct content and formatting with module template', () async { testUsingContext('has correct content and formatting with module template', () async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment