Setting app label based on buildvariant - android

I have an Android with multiple productFlavors. I'd like the android:label for the debug build of, for example flavor1, to say Flavor1 Debug and the release build to say Flavor1.
Without productFlavors this is relatively simple, just create a different string resource in the debug and release source folders but with productFlavors, I don't want to be creating flavor1Debug, flavor2Debug, etc. folders for each flavor with just one string resource in there.
Is there a way to do this ? I'm guessing it requires merging resources somehow but am not sure how.
For extra points, it would be awesome if I could add an overlay to the app icon i.e. merge two images but I realize that might be taking it too far.

I worked out a solution for this. Basically, you use the following method that copies read the build file, modifies the property value, then rewrites the files.
def debugAppRename(variant, labelResource) {
def flavor = variant.productFlavors.get(0)
def buildtype = variant.buildType
// Append buildType name to app label
if(buildtype.debuggable) {
variant.mergeResources << {
def valuesFile = "$buildDir/res/all/${flavor.name}/${buildtype.name}/values/values.xml"
def values = (new XmlParser()).parse(valuesFile)
values.string.each { m->
if (m.#name == labelResource) {
m.value = m.text() + " " + buildtype.name.capitalize()
new XmlNodePrinter(new PrintWriter(new FileWriter(valuesFile)))
.print(values)
}
}
}
}
}
The way to use it is:
applicationVariants.all { variant ->
debugAppRename(variant, 'app_name') // where `app_name` is the string resource you use for the `app:label` property in your AndroidManifest.xml
}
The resulting app will have the buildType name appended to it if it is a debuggable buildType, for e.g. My App Debug, My App Staging, etc. The release build remains unaffected.
I also put it up on a gist.
You can apply it in your build.gradle using the apply from: directive with the raw gist URL or copy the above to your code (personally I find build scripts become too large so I prefer applying from).

Related

Getting an app_name from strings.xml for current product flavor in gradle

In my app, I have a lot of different product flavors, around 10 actually.
The way they are done is something like : eng_testDebug, eng_prodDebug, eng_testRelease, eng_prodRelease e.t.c for other languages.
So now I'v got a task to add "- Test" to the app name for build with test in the build name.
Right now I found a solution to parse string.xml from main sourceSet and build my app name using manifestPlaceHolders in a gradle file. But it works only for main flavor.
Here is a code for getting default strings.xml app_name I am using:
def getAppName() {
def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
String s = new XmlParser().parse(stringsFile).string.find { it.#name.equals 'app_name' }.text();
return s.replaceAll("\"", "");
}
And here I modify it to add "Test":
def getEditedAppName() {
if (getCurrentFlavor().contains("test")) {
return getAppName() + "(Test)";
} else {
return getAppName();
}
}
getCurrentFlavor() returns me a flavor name used in :assemble task.
Basically, the question is how can I get other sourceSet files depending on my current building flavor so I can parse flavor's app_name?
Unfortunately defying an app_name string for each flavor in a gradle file is not an valid option for me.
For each flavor you can create a folder named like the flavor in you src folder. There you should also see your main folder. In those flavor folders you can create the res folder and override or add additional resource files.
Here you can create the strings.xml and change the app name as well as drawables and so on.
http://ptrprograms.blogspot.de/2014/08/build-variations-using-gradle-and.html
Edit based on comment:
I got these two flavors:
productFlavors {
appFlavorA{
applicationId "de.test.appaA"
}
appFlavorB{
applicationId "de.test.appaB"
}
}
Now I got the following structure for strings.xml
ProjctRoot/app/src/main/res/values/strings.xml
ProjctRoot/app/src/appFlavorA/res/values/strings.xml
ProjctRoot/app/src/appFlavorB/res/values/strings.xml
This will result in the usage of the corresponding strings.xml for the flavorflavor
If I understand the question correctly, you want to have different app names based on your product flavors.
In your gradle file:
android {
productFlavors {
stagingone{
}
stagingtwo{
}
}
}
Assuming that you have this sample entry, just place a strings.xml file that contains different app_name for each of the flavors.
src/stagingone/res/values/strings.xml
src/stagingtwo/res/values/strings.xml
I don't think you can manually do that (change app names at runtime), as highlighted in this Stackoverflow thread.
Android : Change App Label Programatically
But, there is another way. Do it the same, but for app launcher icons for the flavors. Good luck.

How to define and use a constant in Gradle build script (Android)?

I'm working on an Android application with Gradle as its build system.
My objective is to use a value (a package name) as an applicationId:
productFlavors {
orange {
applicationId "com.fruits.android.orange"
// ...
But also to expose it via BuildConfig so that Java code has access to it.
This access has to be from outside the flavor (namely, free version of the app needs to know the package name of the paid version so that it can prompt user for an upgrade in Play store).
So I'd like to do something like that:
productFlavors {
orange {
applicationId orangeProPackage
// ...
buildConfigField 'String', 'ORANGE_PRO_PACKAGE', "$orangeProPackage" // ?
Only I'm not sure how to define orangeProPackage so that it's visible in the entire build.gradle and doesn't break the script.
Since there's a few different flavors, it would be best if I could somehow group all these constants like that (I guess?):
def proPackages = [
orange: "..."
apple: "..."
banana: "..."
]
and then refer to them in a clean and descriptive manner like proPackages.orange etc.
The question is, how to accomplish that?
This is not a duplicate of Is it possible to declare a variable in Gradle usable in Java?
I've seen that question (and a few others). I know how to declare buildConfigFields, I already have plenty. My question is about reusing the same value as a buildConfigField and applicationId.
Only I'm not sure how to define orangeProPackage so that it's visible in the entire build.gradle and doesn't break the script.
You could put it in gradle.properties in your project root. Like other .properties files, it's just a key-value store:
ORANGE_PRO_PACKAGE=com.morawski.awesomeapp
You then refer to it as a simple global string variable (ORANGE_PRO_PACKAGE) in your build.gradle:
buildConfigField 'String', 'ORANGE_PRO_PACKAGE', '"' + ORANGE_PRO_PACKAGE + '"'
it would be best if I could somehow group all these constants
Anything involving .properties files won't handle that. There, you may be looking at defining globals in the top-level build.gradle file just in plain Groovy code or something.
you could use extentions like this:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.6.10'
ext.app_version_code = 1010
ext.app_version_name = '1.0.1.0'
}

Android Gradle Read App Name from strings.xml

I am trying to rename my APK files for each build variant to include the application name, versionName, versionCode and build number when present. So far I have everything working except the application name.
I want to use the same value that the AndroidManifest.xml file uses for android:label. This comes from a string resource #string/app_name. I have seen the ability to replace the resource values by using:
resValue "string", "app_name", "Some new value"
But I would just like to read this value and use it to name my APK file.
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
renameApk(variant, output)
}
}
def renameApk(variant, output) {
def apkPath = output.outputFile.parent
def baseName = project.archivesBaseName
baseName += "-${variant.buildType.name}"
// add version name and version code
baseName += "-v${variant.mergedFlavor.versionName}-${variant.mergedFlavor.versionCode}"
// if built on jenkins ci, add jenkins build number:
def buildNumber = System.getenv('BUILD_NUMBER')
if (buildNumber && buildNumber.size() > 0) {
baseName += "-b${buildNumber}"
}
// if the variant will not be zipAligned, specify that
if (!output.zipAlign) {
baseName += '-unaligned'
}
// set the output file
output.outputFile = new File(apkPath, "${baseName}.apk");
}
I don't see any method in Android Plugin docs for accessing resources, so here is the code you can use to find your app's name by searching resources:
def getAppName() {
def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
return new XmlParser().parse(stringsFile).string.find { it.#name.equals 'app_name' }.text()
}
BUT I completely agree with #Samuil Yanovski in that it is not worth it - better hardcode a string. I don't think it will slow down building process, but it is just unnecessary.
I don't think this can be done easily. Resource resolution is done on the mobile device to accommodate for things like screen orientation, localization and so on. The Gradle build system has no way of knowing which locale to use for example. If you insist on getting the value from the resources, you can open the specific strings.xml file you'd like to use, parse the XML and get the value yourself. In my opinion this is a huge overkill and would be pretty slow and ugly.
App name is not changed often, so I would be comfortable with having it hardcoded (especially since the apk file name is not visible to the end user, so even if mistakes happen, the impact would be minimal). If you are working on a white label application and have to support dynamic app name, extracting the value to the gradle.properties file (or some other type of configuration file, you are using) should be a better option rather than using the app's resources.
I have create method using #Yaroslav's answer (https://stackoverflow.com/a/37432654/6711554).
def getApplicationName() {
try {
def stringsFile = file("./src/main/res/values/string.xml")
return new XmlParser().parse(stringsFile).string.find { it.#name.equals 'your_app_name' }.text()
}catch(e){
println(e)
return "Default App Name"
}
}
You can read any string in your gradle from your any resource file.

Android Studio: Gradle Product Flavors: Define custom properties

I am building different product flavors of an Android App in Gradle (Android Studio).
Hence I defined the following product flavors:
android {
project.ext.set("customer", "")
project.ext.set("server", "")
//Configuration happens here - code removed for readability
buildTypes {
debug {
server = "test"
}
release {
server = "release"
}
}
//Available product flavors
productFlavors {
customerA{
customer = "a"
}
customerB{
customer = "b"
}
customerC{
customer = "c"
}
}
}
However, later on, when I access the defined project property "customer" (whose value is set in the product flavor i am currently building) in one of my build tasks, it always has the value "c" even though iam building customerA (in which case the property customer should be "a" rather than "c"). For instance I execute the following task later on:
preBuild << {
println "Building customer: " + customer
}
and it always prints:
Building customer: c
So i am guessing there is some overwriting happening? Possibly related to the configuration VS execution phase? Not sure how/why though, so any help is be greatly appreciated.
UPDATE: Alternatively it would already get me further to determine the name of the product flavor (without the build type name attached to it) and the build type (again: without the product flavor name prepended to it) during execution phase of the gradle build.
Considering the above configuration the expected product flavor names would be: customerA, customerB and customerC.
During evaluation phase, Gradle executes all of the code in your android block; it doesn't just execute the code relevant to the flavors you want to compile. In fact, during evaluation phase, it doesn't even really know what your flavors are; it has to evaluate that to find out.
So all three of your lines customer = "a", customer = "b", and customer = "c" will get executed.
This is one of the subtle things about Gradle that make it a little difficult to learn.
So I've explained why your code isn't working the way you expect, but this answer is incomplete because I haven't said a lot about what to do to make it work right, but it's hard to say what to do because I'm not sure what you're trying to accomplish. In general I can say that you should think of trying to accomplish what you want using user-defined tasks, and setting up intra-task dependencies to make sure things get executed in the right order. A gotcha with Android Gradle builds is that even those tasks don't get defined until evaluation phase (it can't know what tasks it needs to build all your flavors until it's evaluated the build file and knows what those flavors are), so do some SO sleuthing to see how to hook things onto Android Gradle build tasks -- you have to set up your tasks at the end of evaluation phase after the Android plugin has done its thing.
A lot of thanks goes to Scott Barta, for his suggestions and for explaining, why my solution did not work (which also made me reconsider a few things). I basically came up with different ways to accomplish what I needed.
Unless what you need to do can't be achieved by simply organizing your Android Resource tree based on build types and flavors (i.e. via convention) then I'd recommend option 2. Though I did keep option 1 for reference purposes since it covers the interesting subject of productFlavor property extension.
Custom property-based option: Product Flavors lets you define custom properties and thus extend a productFlavor. An example is provided here by Xavier Ducrohet: https://stackoverflow.com/a/17708357/1041533
I'll offer up a very simple and similar example as provided above, though in my case I needed a String property, rather than a boolean.
// This class will be used to create our custom property
class StringExtension {
String value
StringExtension (String value) {
this.value = value
}
public void setValue(String value) {
this.value = value
}
public String getValue() {
return value
}
}
android {
// Add our new property to each product flavor upon creation
productFlavors.whenObjectAdded { flavor ->
//I am suspecting the last argument is the default value
flavor.extensions.create("myProperty", StringExtension , '')
}
// then we can set the value on the extension of any flavor object
productFlavors {
customerA{
myProperty.value 'customerA'
}
customerB{
myProperty.value 'customerB'
}
}
}
//Adds a custom action to the preBuild task
preBuild << {
//Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
android.applicationVariants.all { variant ->
//Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
variant.productFlavors.each { flavor ->
//Access our custom property "customerName"
println "Building customer" + flavor.customerName.value
}
}
}
I then realized, that the above was totally unnecessary, because all I wanted was the name of my flavor (without the build type in it) and once I found the property that gives me the name of my flavor, I was able to change all of the above code as follows:
Simply use the name of your flavor as the customer's name by accessing the already existent product flavor property called "name".
android {
productFlavors {
customerA{
}
customerB{
}
}
}
//Adds a custom action to the preBuild task
preBuild << {
//Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
android.applicationVariants.all { variant ->
//Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
variant.productFlavors.each { flavor ->
//Access our product flavor name
println "Building customer" + flavor.name
}
}
}
The above makes a lot more sense too, because my directory structure for Android Resources is named after the actual flavors.
The latter also led me to my final solution for the original question:
Resource directory based approach
The intent was to modify a file in the xml folder of each customer based on whether it is a release or a debug build. This can be achieved by a corresponding folder structure. Based on the original question we have 3 customers, and each customer has a debug and a release build. The afore mentioned xml files are different for each customer and build type. Hence the following directory structure:
src/
- customerA
//Contains all relevant resource files specific to customer A
- customerB
//Contains all relevant resource files specific to customer B
- customerC
//Contains all relevant resource files specific to customer C
- customerADebug
//Contains debug server-settings file for customer A
- customerBDebug
//Contains debug server-settings file for customer B
- customerCDebug
//Contains debug server-settings file for customer C
- customerARelease
//Contains release server-settings file for customer A
- customerBRelease
//Contains release server-settings file for customer B
- customerCRelease
//Contains release server-settings file for customer C
So the main content for each product flavor was in the folder with the same name as the flavor (customerA, customerB etc. see first part of above snippet). Now this one file, that different based on whether it was a debug or release build for each customer is put into the appropriate folders such as customerADebug --> contains file with server settings for debug mode etc.
And when you build customerA for instance the correct file will be chosen if you build a debug or release build.
To answer the UPDATE part of my post:
Product flavor name (without buildType):
flavor.name (where flavor is a productFlavor)
The following worked for me to add custom properties to product flavors:
android {
// ...defaultConfig...
productFlavors.whenObjectAdded { flavor ->
// Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
flavor.ext.set('myCustomProperty', 'customPropertyValue')
}
productFlavors {
flavor1 {
}
flavor2 {
myCustomProperty = 'alternateValue'
}
}
}
flavor1 has the default value for the custom property, while flavor2 has the overridden value.
Here's an example how to access the custom property:
applicationVariants.all { variant ->
// Get the 'myCustomProperty' property from the variant's productFlavor (it's a list, but there should only be one)
def customProp = variant.productFlavors*.myCustomProperty[0]
}
I assume the same could be done to add custom properties to build types, but I haven't tested this.

Override resources with gradle depending on buildType

I want to override some strings in my res/strings.xml with gradle.
I know that since Android Gradle Plugin 0.7.+ theres the possibilty to have a variant specific source folder.
But my app has a lot of flavors and I don't want to add additionally variant specific folders.
UPDATE 2014-01-17
What I want in detail:
I have some variables in my Resources that are depending only by the buildType (e.g. "release").
First I thought my SOLUTION_1 (override data after resources were merged) is nice, because if I have to change these variables I just have to change them in the build.config (just one place).
But as Scott Barta wrote in the comment below there are some good reasons why this solution is NOT a good idea.
So i tried another solution SOLUTION_2 (just merge the right resources) based on this GitHub project of shakalaca. I think this way is more elegant and I still have the advantage just to change the variables in one place!
SOLUTION_1 (override data after resources were merged):
What I did in AS 0.4.2:
in build.gradle I try to override the string "Hello World" to "OVERRIDE" (based on my answer at this post):
android.applicationVariants.all{ variant ->
// override data in resource after merge task
variant.processResources.doLast {
overrideDataInResources(variant)
}
}
def overrideDataInResources(buildVariant){
copy {
// *** SET COPY PATHS ***
try {
from("${buildDir}/res/all/${buildVariant.dirName}") {
// println "... FROM: ${buildDir}/res/all/${buildVariant.dirName}"
include "values/values.xml"
}
} catch (e) {
println "... EXCEPTION: " + e
}
into("${buildDir}/res/all/${buildVariant.dirName}/values")
// println "... INTO: ${buildDir}/res/all/${buildVariant.dirName}/values"
// --- override string "hello_world"
filter {
String line ->
line.replaceAll("<string name=\"hello_world\">Hello world!</string>",
"<string name=\"hello_world\">OVERRIDE</string>");
}
// *** SET PATH TO NEW RES ***
buildVariant.processResources.resDir = file("${buildDir}/res/all/${buildVariant.dirName}/values/values/values.xml")
// println "... NEW RES PATH: " + "${buildDir}/res/all/${buildVariant.dirName}/values/values/values.xml"
}
}
The copy and filter task works fine, but I couldn't set the "new" values.xml as string resource.
SOLUTION_2 (just merge the right resources)
define a floavor for specific buildType (e.g. "releaseRes")
merge this resourses with the flavor you want to build:
android.applicationVariants.all{ variant ->
variant.mergeResources.doFirst{
checkResourceFolder(variant)
}
}
def checkResourceFolder(variant){
def name = variant.name;
if(name.contains("Release")){
android.sourceSets.release.res.srcDirs = ['src/releaseRes/res']
android.sourceSets.flavor1.res.srcDirs = ['src/flavor1/res']
}
}
You should strive to come up with a solution that doesn't involve writing any custom code in your build files, especially code that does tricky things with reassigning source sets on the fly. Custom Gradle code is a little funky to write, and it's difficult to debug and maintain. The new build system is extremely powerful and already has tons of flexibility, and it's likely that you can already do what you want; it's just a matter of learning how.
Especially if you're just learning the ins and outs of Android-Gradle projects (and it's so new that we all are), it's best to try hard to work with the functionality built into the system before thinking outside the box.
Some recommendations:
It's unlikely you need to vary resources based on build type. A build type in Android-Gradle is supposed to be something like debug or release, where the difference is in debuggability, compiler optimization, or signing; build types are supposed to be functionally equivalent to each other. If you look at the properties you can set on a build type through the Groovy DSL, you can see the intent: debuggable, jniDebugBuild, renderscriptDebugBuild, renderscriptOptimLevel, packageNameSuffix, versionNameSuffix, signingConfig, zipAlign, runProguard, proguardFile, proguardFiles.
If you still think you want to vary resources based on build type, there's already an easy way to do that with the current build system. You can have a build-type-specific resource directory, put your resources in there, and the resource merging in the build system will take care of things for you at build time. This is one of the powerful features in Android/Gradle. See Using Build Flavors - Structuring source folders and build.gradle correctly for information on how to make that work.
If you want to vary something based on build type and your needs are very quick and simple, you might want to do the switch in Java code instead of resources and instead of in the build system. There's the BuildConfig mechanism for that sort of thing -- it's a Java class that defines a DEBUG flag based on debug/release build status, and you can add your own custom Java code from different build types to do more meaningful things. BuildConfig was intended for allowing small functional differences between build types, for cases where a debug build might want to perform some wasteful operation to assist in development, like doing more extensive data validation or creating more detailed debug logging, and those wasteful things are best optimized out of release builds. Having said that, it might be an appropriate mechanism to do what you want.
Consider using flavors for what you're using build types for now. Conceptually a flavor is kind of like a build type in that it's another variant of your application that can be built; the build system will create a matrix of flavors vs. build types and can build all combinations. However, flavors address a different use case, where different flavors share most code but can have significant functional differences. A common example is a free vs. paid version of your application. Inasmuch as a different resource in different variants of your app represents different functionality, that might indicate a need for a different flavor. Flavors can have different resource directories that are merged at build time in the same way as build configs; see the question linked above for more info.
I don't believe you need to customize the build script at all to achieve what you want. According to my reading of http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants; when the build runs, resources will be merged from the following folders, if they exist;
src/[flavour][buildType]/res
src/[buildType]/res
src/[flavour]/res
src/main/res
So I believe you can achieve what you want by simply add the resources in src/release/res.
Though you can tweak the folder names by specifying the relevant sourceSets.[type].res.srcDirs if you really want to change them.
If anyone stumble upon this
buildTypes {
debug{
buildConfigField "String", "Your_string_key", '"yourkeyvalue"'
buildConfigField "String", "SOCKET_URL", '"some text"'
buildConfigField "Boolean", "LOG", 'true'
}
release {
buildConfigField "String", "Your_string_key", '"release text"'
buildConfigField "String", "SOCKET_URL", '"release text"'
buildConfigField "Boolean", "LOG", 'false'
}
}
And to access those values using build variants:
if(!BuildConfig.LOG)
// do something with the boolean value
Or
view.setText(BuildConfig.yourkeyvalue);

Categories

Resources