I am trying to migrate to Android Gradle Plugin 7.
I have the following code that needs migration:
applicationVariants.all { variant ->
// do some processing to obtain string apiKeyValue
variant.resValue "string", "api_key", apiKeyValue
}
I have looked at the migration blog post here android developers blog but still no clear reference to how to create dynamic
resValue
Any thoughts?
I also tried to use something similar to
androidComponents {
onVariants(selector().all(), { variant ->
// do some processing to obtain string apiKeyValue
addResValue("api_key", "string", apiKeyValue, "Value from variant")
})
}
But no luck as
addResValue
method is not found.
After a bit of searching and trial and error, the following code worked for me:
androidComponents {
onVariants(selector().all(), { variant ->
// do some processing to obtain string apiKeyValue
variant.resValues.put(variant.makeResValueKey("string", "api_key"), new ResValue(apiKeyValue, "Variant Name"))
})
}
The above code is a snippet from my build.gradle file. I took inspiration from here gradle-recipes
For groovy and AGP 7.2.1 old way still works. Only keys for resValues now have type prefix ie "string/app_name". instead of "app_name".
android{
applicationVariants.all{ variant ->
def strRes = variant.mergedFlavor.resValues.get("string/app_name")
if(strRes != null){
println( "string/app_name = " + strRes.getValue())
variant.resValue 'string', 'app_name', "${strRes}New"
}
}
}
Related
Before AGP 7.0.0-alpha15 I could change version code of an app for example like that
android {
defaultConfig {
applicationVariants.all {
if (buildType.name == "debug") {
outputs.forEach { output ->
(output as? com.android.build.gradle.internal.api.ApkVariantOutputImpl)?.versionCodeOverride = 1
}
}
}
}
}
applicationVariants seems missing after AGP 7.0.0-alpha15, how to change it?
PS: It seems ok in plain gradle, above is Kotlin
Edit
With answer from below I was able to override version code in build:
android {
androidComponents.onVariants { appVariant ->
if (appVariant.buildType == "release") {
appVariant.outputs.forEach {
it.versionCode.set(1)
}
}
}
}
The Variant API is going to change to a lazily-evaluated model in AGP 7.0.0, and it seems like Alpha 15 has removed the old APIs for this now. Going forward, you will need to use the androidComponents DSL, which gives access to variants. I am not sure if you'll be able to rewrite the version code like this, however. Check out beforeVariants and onVariants for more info:
android {
androidComponents.beforeVariants { variantBuilder ->
// Callback before variants are built. Can be modified, but doesn't allow access to outputs
}
androidComponents.onVariants { variant ->
// Callback after variants are built. Apparently it's read-only access at this point, but outputs are available here
println(variant.outputs)
}
}
In Java plugin code, one can set the version code & name alike this:
class SomePlugin implements Plugin<Project> {
#Override
#SuppressWarnings("UnstableApiUsage")
public void apply(#NotNull Project project) {
ApplicationAndroidComponentsExtension androidComponents = project.getExtensions()
.getByType(ApplicationAndroidComponentsExtension.class);
androidComponents.finalizeDsl(extension -> {
for (AndroidSourceSet sourceSet : extension.getSourceSets()) {
System.out.println(sourceSet.getName());
}
});
int versionCode = 1;
String versionName = "1.0.0";
VariantSelector selector = androidComponents.selector().all();
androidComponents.onVariants(selector, variant -> {
for (VariantOutput variantOutput : variant.getOutputs()) {
variantOutput.getVersionName().set( versionName );
variantOutput.getVersionCode().set( versionCode );
System.out.println(">>>> " +
variant.getName() + " " +
variantOutput.getVersionCode().get() + " / " +
variantOutput.getVersionName().get());
}
});
}
}
finalizeDsl happens before onVariants.
android {
// ..
splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
}
}
// ..
}
androidComponents {
onVariants(selector().all()) { appVariant ->
for (VariantOutput variantOutput : appVariant.getOutputs()) {
def versionCodes = ['[armeabi]' : 100000,
'[armeabi-v7a]' : 200000,
'[x86]' : 300000,
'[arm64-v8a]' : 400000,
'[x86_64]' : 500000]
String abi = variantOutput.getFilters().identifier;
Integer version = versionCodes.get(abi) + versionCode
if (appVariant.getName().contains("Debug")) {
version += 10000
}
variantOutput.getVersionCode().set(version);
}
}
}
dependencies {
// ...
}
I'm trying to configure an Android library project to deploy multiple artifacts to a locally hosted Maven repository. I've gotten far enough such that both artifacts have their own POM generated, and it gets deployed properly to the repo, with the following script:
android {
// Publish both debug and release
publishNonDefault true
}
uploadArchives {
repositories.mavenDeployer {
def majorVersion = 1
def minorVersion = 1
def buildVersion = project.properties.get('RELEASE', '0').toInteger()
addFilter('release') { artifact, file ->
file.name.contains('release')
}
addFilter('debug') { artifact, file ->
file.name.contains('debug')
}
activePomFilters.each { filter ->
pom(filter.name) {
groupId = 'com.redacted'
artifactId = 'redacted'
packaging = 'aar'
version = "${majorVersion}.${minorVersion}.${buildVersion}"
if (!project.hasProperty('RELEASE')) {
version += "-SNAPSHOT"
}
if (filter.name == 'debug') {
artifactId += '-debug'
}
}
}
}
}
The expected delivery is:
com/
redacted/
redacted/
1.1.0-SNAPSHOT/
redacted-debug/
1.1.0-SNAPSHOT/
Which happens as expected, but it seems to publish the artifacts with an additional suffix (which breaks the dependency discovery), and I cannot figure out where it is coming from, or how to change it. What I see is:
com/redacted/redacted/1.1.0-SNAPSHOT/
redacted-1.1.0-20150717.213849-1-release.aar
redacted-1.1.0-20150717.213849-1-release.aar.md5
redacted-1.1.0-20150717.213849-1-release.aar.sha1
redacted-1.1.0-20150717.213849-1.pom
redacted-1.1.0-20150717.213849-1.pom.md5
redacted-1.1.0-20150717.213849-1.pom.sha1
For some reason, it's appending the date, as well as a -release suffix to only the AAR-related files, but not the POM files. If I manually rename these files, everything works as expected. For example, this is what I expect to be output:
com/redacted/redacted/1.1.0-SNAPSHOT/
redacted-1.1.0-20150717.213849-1.aar
redacted-1.1.0-20150717.213849-1.aar.md5
redacted-1.1.0-20150717.213849-1.aar.sha1
redacted-1.1.0-20150717.213849-1.pom
redacted-1.1.0-20150717.213849-1.pom.md5
redacted-1.1.0-20150717.213849-1.pom.sha1
How can I change how these files are delivered?
What you are running in to is this (emphasis mine):
Important: When enabling publishing of non default, the Maven publishing plugin will publish these additional variants as extra packages (with classifier). This means that this is not really compatible with publishing to a maven repository. You should either publish a single variant to a repository OR enable all config publishing for inter-project dependencies.
See the documentation: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Library-Publication
The suffixes release and debug that you see are the classifiers introduced by enabling publishing of non-default artifacts. The <artifact> elements in build/ivy.xml, which is used as the basis for the Maven configuration, contain these classifiers.
Iterating over the artifacts in the configurations and removing the classifier does not work. Although setting the classifier is allowed, its original value is kept.
But what does work is wrapping the original artifacts. The wrapper will always return null for a classifier. This does result in the release and debug artifact having the same fully-qualified ID (= name + classifier), which results in only one artifact being published. This can be fixed by using a different name for debug artifacts:
class UnclassifiedPublishArtifact implements PublishArtifact {
private PublishArtifact delegatee;
private boolean isDebugArtifact;
UnclassifiedPublishArtifact(PublishArtifact delegatee, isDebugArtifact) {
this.delegatee = delegatee
this.isDebugArtifact = isDebugArtifact
}
#Override
String getName() {
return delegatee.name + (isDebugArtifact ? '-debug' : '')
}
#Override
String getExtension() {
return delegatee.extension
}
#Override
String getType() {
return delegatee.type
}
#Override
String getClassifier() {
return null
}
#Override
File getFile() {
return delegatee.file
}
#Override
Date getDate() {
return delegatee.date
}
#Override
TaskDependency getBuildDependencies() {
return delegatee.buildDependencies
}
}
project.afterEvaluate {
configurations.each { configuration ->
def artifacts = configuration.artifacts
if (!artifacts.isEmpty()) {
def unclassifiedArtifacts = []
unclassifiedArtifacts.addAll(artifacts.collect { classifiedArtifact ->
new UnclassifiedPublishArtifact(classifiedArtifact, classifiedArtifact.classifier == 'debug')
})
artifacts.clear()
artifacts.addAll(unclassifiedArtifacts)
}
}
}
I can't quite understand from the documentation what the consequences are for project dependencies, so you should check if these still work.
I declared this function in my Android project build.gradle:
def remoteGitVertsion() {
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parse(new URL("https://api.github.com/repos/github/android/commits"))
assert object instanceof List
object[0].sha
}
And this flavor:
android {
...
productFlavors {
internal {
def lastRemoteVersion = remoteGitVersion()
buildConfigField "String", "LAST_REMOTE_VERSION", "\"" + lastRemoteVersion + "\""
}
...
}
...
}
Now, due to gradle declarative nature, the remoteGitVersion function is executed every time the project is built, it doesn't matter if the build flavor is internal or something else. So, the github API call quota is consumed and, after a little while, I receive a nice forbidden message.
How can I avoid this? Is it possible to execute the function only when the selected flavor is the right one?
Took reference from here:
In Android/Gradle how to define a task that only runs when building specific buildType/buildVariant/productFlavor (v0.10+)
To recap:
1. Wrap your flavor specific logic into a task
task fetchGitSha << {
android.productFlavors.internal {
def lastRemoteVersion = remoteGitVersion()
buildConfigField "String", "LAST_REMOTE_VERSION", "\"" + lastRemoteVersion + "\""
}
}
2. Make the task being called whenever you build your variant, and only then.
You could use assembleInternalDebug to hook into, in your case.
tasks.whenTaskAdded { task ->
if(task.name == 'assembleInternalDebug') {
task.dependsOn fetchGitSha
}
}
3. Make sure to remove the dynamic stuff from your flavor definition
productFlavors {
internal {
# no buildConfigField here
}
}
Hope that helps.
I'm building my Android app with the Gradle plugin. I'm using the flavors feature to create four different variants. At the moment, it's a single dimension with four values, but it would be more logical to set it up as two dimensions with two values each. I've got that building, but this is where I run into trouble.
I need each of the four variants to have a different package name. The first three are easy since I have a default and each dimension can override the package, but I need to set a package name for when both non-default dimensions are in play.
flavorDimensions "foo", "bar"
productFlavors {
boring.flavorDimension "foo"
charm {
flavorDimension "foo"
packageName "com.example.charm"
}
strange {
flavorDimension "bar"
packageName "com.example.strange"
}
// This is the idea of what I want, but it doesn't work because there
// must be only a single dimension specified here.
charmStrange {
flavorDimension "foo", "bar"
packageName "com.example.charminglystrange"
}
}
I tried setting it after declaration by looking up the composed flavor variant, but I didn't have much luck. I'm not very familiar with Gradle, so I'm sure there's trickery I haven't employed. Alternately, maybe I could specify the package name in src/charmStrange/AndroidManifest.xml and let the merge sort it out? That seems like it could cause problems in the future.
I had a similar question this answer isn't tested in my case I was appending the product flavor to the app id set by the other dimension, using your example it would append .strange to package com.example.charm
android.applicationVariants.all { variant ->
def flavorString = variant.getVariantData().getVariantConfiguration().getFlavorName()
def mergedFlavour = variant.getVariantData().getVariantConfiguration().getMergedFlavor();
def appId = variant.getVariantData().getVariantConfiguration().getApplicationId();
if(flavorString.contains("Strange")) {
mergedFlavour.setApplicationId(appId + ".strange")
}
}
but in your case you want a full package name so something like
android.applicationVariants.all { variant ->
def flavorString = variant.getVariantData().getVariantConfiguration().getFlavorName()
def mergedFlavour = variant.getVariantData().getVariantConfiguration().getMergedFlavor();
def appId = variant.getVariantData().getVariantConfiguration().getApplicationId();
if(flavorString.contains("charmStrange")) {
mergedFlavour.setApplicationId("com.example.charminglystrange)
}
}
charmStrange might need to be CharmStrange, try playing with it
You can now set applicationIdSuffix for productFlavors:
android {
productFlavors {
free {
applicationIdSuffix = ".free"
}
prod {
}
}
}
Source:
http://android-developers.blogspot.ie/2015/12/leveraging-product-flavors-in-android.html
You will want to set the applicationId, not the packageName:
productFlavors {
boring.flavorDimension "foo"
charm {
flavorDimension "foo"
applicationId "com.example.charm"
}
strange {
flavorDimension "bar"
applicationId "com.example.strange"
}
// This is the idea of what I want, but it doesn't work because there
// must be only a single dimension specified here.
charmStrange {
flavorDimension "foo", "bar"
packageName "com.example.charminglystrange"
}
}
I am developing an app for Android that connects to my server. As usual my server during development is on a laptop and its ip is changing. I would like to detect ip address of my development machine i.e.
InetAddress.getLocalHost().getCanonicalHostName()
...and inject it into android strings.xml file so my Android app would connect to right ip during development.
I am struggling with determining where to get info what kind of buildType is run following part can be executed each time:
android {
buildTypes.each { buildType ->
if(buildType.name == 'debug') {
def host = InetAddress.getLocalHost().getCanonicalHostName()
}
}
}
Let's say that that host is determined right now. However I am still not sure how to replace host in strings.xml file in order to connect to the right host.
I cannot call processResources as following exception is thrown:
Could not find method processResources() for argument [build_11onjivreh0vsff0acv5skf836$_run_closure3#cbbe2cf] on project
Any suggestion or source code would be appreciated.
There is an much easier solution available: Use a static final in BuildConfig:
buildTypes {
debug {
def host = InetAddress.getLocalHost().getCanonicalHostName()
buildConfig "public static final String API_HOSTNAME = \"" + host + "\";"
}
release {
buildConfig "public static final String API_HOSTNAME = \"whateveritisforreleasebuilds\";"
}
}
BuildConfig.API_HOSTNAME will hold the remote address.
The following Syntax is required for the latest versions:
buildTypes {
debug {
buildConfigField "String", "ENVIRONMENT", "\"development\""
}
release {
buildConfigField "String", "ENVIRONMENT", "\"production\""
}
}
alternatively with a boolean type:
buildTypes {
debug {
buildConfigField "boolean", "ENVIRONMENT", "true"
}
release {
buildConfigField "boolean", "ENVIRONMENT", "false"
}
}
You would then use this as
BuildConfig.ENVIRONMENT.equals("production"); // String type
or
if (BuildConfig.ENVIRONMENT) { ... } // boolean type