Android Studio: Gradle Product Flavors: Define custom properties

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.


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)


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.