Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Studio: Gradle Product Flavors: Define custom properties

Tags:

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.

like image 722
AgentKnopf Avatar asked Jan 30 '15 08:01

AgentKnopf


2 Answers

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.

like image 198
Scott Barta Avatar answered Oct 05 '22 11:10

Scott Barta


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.

  1. 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:

  1. 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:

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

like image 41
AgentKnopf Avatar answered Oct 05 '22 09:10

AgentKnopf