Android Studio: Gradle Product Flavors: Define custom properties - android

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.

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.

Android App Name Per Build Type AND Flavour

My application is having multiple build types and flavours gradle.
buildTypes {
release {}
test {}
debug {}
}
productFlavors {
europe {}
asia {}
}
How can I name the app according to the combination of build type and flavor?
Example:
Flavour europe will have app name AppEurope
BuildType test will add "Test" suffix behind the app name, AppEuropeTest
I was facing the same problem within my watch face and tried to combine flavor dependent application names with build type dependent application label values. I ended up doing it as follows:
Use manifestPlaceholder in the build.gradle to inject buildType specific string resource links:
In the build.gradle file:
buildTypes {
release {
manifestPlaceholders = [ applicationLabel: "#string/app_name"]
}
debug {
manifestPlaceholders = [ applicationLabel: "#string/app_name_dev" ]
}
}
In the AndroidManifest.xml:
<application
[..]
android:label="${applicationLabel}">
In the strings.xml file:
<resources>
<string name="app_name">Classic & Essential</string>
<string name="app_name_dev">Classic & Essential (DEV)</string>
</resources>
Use flavor specific versions of string.xml resource files overriding the values for the flavor.
I also described this in one of my blog posts:
https://www.journal.deviantdev.com/android-build-type-app-name-label/
Your questions has been answered here -
How to change app name per Gradle build type
Create separate versions of string.xml for each build type for all
the flavours.
There is a a sample project here.
https://github.com/commonsguy/cw-omnibus/tree/master/Gradle/HelloBuildType
Edit 1
android.sourceSets.europe {
res.srcDirs = ['flavor_resources/europe/res']
}
android.sourceSets.europe.debug {
res.srcDirs = ['flavor_resources/europe/debug/res']
}
Related link shows how to do this for flavors, and the concept is identical when expanding to include build types - we can think of both flavors and build types as combining to create "variants", and we can configure variants as easily as the flavors that make them.
Assume:
You have these build types:
release
test
debug
You have these flavors:
europe
asia
You have a default app_name string resource declared in the normal place
src/main/res/values/strings.xml
<string name="app_name">App</string>
Manifest file:
There is no need for manifest placeholders in this simple case. If your case requires, you can configure them as per the other answers.
Simply use app_name in the manifest directly. Trust that the changes you make for the variants will reflect correctly.
e.g.
<application
android:label="#string/app_name"
etc...
Different names for variants:
As standard, gradle allows for resource files to be declared at in variant source folders (like the flavor or build type source folders) and these will automatically override the defaults.
There is no need for source sets in this simple case.
Simply add a new strings.xml file for each variant, redefining the app_name to match that variant.
e.g. For the Europe Test build, add your file at the variant folder named europeTest, and override the name:
src/europeTest/res/values/strings.xml
<string name="app_name">AppEuropeTest</string>
Different variants will also get their own override strings files as required.
App name suffix per flavor:
With app ID, each flavor can be configured to add a bit to the app ID.
This is not currently possible with the normal build system, so you cannot currently configure the test build to append the name "Test" to the main name, and then configure the europe flavor to append "Europe".
That would be really nice, but is not supported natively.
This answer suggests a library that you can use for an alternative way of combining the app names, and could probably be used to make a more logical naming system, especially with more flavor dimensions (similar to how one would do it for application ID).

Process resources for android gradle production Flavors

I'm looking for the way how to load my properties per product flavor.
I do have right now:
productFlavors {
flavor1 {
println 'Flavor 1'
loadProperties('flavor1.properties')
}
flavor2 {
println 'Flavor 2'
loadProperties('flavor2.properties')
}
I'm using that properties to filter/process some resources before build.
If I do configure my build for one flavor, and run it through Android studio - I could find that two flavors executed and there are two println in console:
Flavor 1
Flavor 2
Is that possible somehow to find active product flavor per build?
Something like get active (or current) product flavor and get access to all properties there?
Or even find a way to process resources per different flavors?
Or may be define some property underFlovors and re use it in another place?
Found the way how to get current build flavor:
android.applicationVariants.all{ variant ->
variant.processResources.doFirst {
println '' + project.getName() + ', flavorName: ' + flavorName
//Processing my resources there
}
}

Setting app label based on buildvariant

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).

Separate source files per build variant in Gradle (Android)?

Our application has a free and a paid version. We also make branded-versions, which means that the application vary in two dimensions.
Four versions could be:
The App, Nike ed. free
The APP, Nike ed. paid
The App, Adidas ed. paid
The App, Adidas ed. free
My solution now is to have two build-types, paid and free:
buildTypes {
paid {
packageNameSuffix ".paid"
}
free {
packageNameSuffix ".free"
}
}
And two build flavors:
productFlavors{
nike{
packageName "com.example.theapp.nike"
}
adidas{
packageName "com.example.theapp.adidas"
}
}
Every free-version of the app make us of a content-provider, a content provider which is specific per flavor-build type combination. The problem is that I don't understand where to put a source file based on build variant. Source files put into /src/nike or /src/free will be picked up depending on build type or flavor. But how about source files that are depending on the build variant (the combination of type and flavor)?
You can create a new folder under src for every build or flavor that you have. i.e: 'free', 'paid', 'nike', 'adidas'.
The files that you put in any of these folders gets picked up when building depending on the type and build both.
According to Gradle Plugin User Guide on Android Tools Project Site:
Similar to Build Types, Product Flavors also contribute code and
resources through their own sourceSets.
and
The following rules are used when dealing with all the sourcesets used
to build a single APK:
All source code (src/*/java) are used together as multiple folders generating a single output.
Manifests are all merged together into a single manifest. This allows Product Flavors to have different components and/or
permissions, similarly to Build Types.
All resources (Android res and assets) are used using overlay priority where the Build Type overrides the Product Flavor, which
overrides the main sourceSet.
Each Build Variant generates its own R class (or other generated source code) from the resources. Nothing is shared between
variants.
meaning that your java files for the buildType "free" will overwrite the ones for your flavors "nike" if they have the same name.
But if you're adding something to a manifest, according to the second point in the list above the final manifest will be a merge of all of the manifests.
If you need more customization you can put your files in your build variant's folder "src/freeNike/".
I had similar problem with build types overriding flavors due to the overlay rules.
I ended up redirecting the build type source sets into different folders depending on which flavor was built.
android.applicationVariants.all { variant ->
switch (variant.name) {
case "FreeNike":
variant.mergeResources.doFirst {
android.sourceSets.free.setRoot("src/freeNike")
}
break;
case "FreeAdidas":
variant.mergeResources.doFirst {
android.sourceSets.free.setRoot("src/freeAdidas")
}
break;
case "PaidNike":
variant.mergeResources.doFirst {
android.sourceSets.paid.setRoot("src/paidNike")
}
break;
case "PaidAdidas":
variant.mergeResources.doFirst {
android.sourceSets.paid.setRoot("src/paidAdidas")
}
break;
}
}
You are of course free to use a different folder structure. See example here: Folder naming convention for gradle build variants
Have a look at newest Gradle plugin it now allows to have variant specific resources
http://tools.android.com/tech-docs/new-build-system
And here You have example of usage
https://android.googlesource.com/platform/tools/build/+/master/tests/overlay3/
Have you tried to put the srcDir in the sourceSets ?
Like so:
sourceSets {
main {
java {
srcDirs 'src/java'
}
}
}
That should output a javaResources with two source codes, nike and adidas.

Categories

Resources