Android Studio: product flavor combination with more than two flavor dimensions (flavor groups)
C

2

29

I am developing an Android application using Android Studio (v 2.1, gradle plugin v 2.1.0). My application has various versions which share a lot of common code so I decided to use flavor dimensions and product flavors to customize code and resources when and where it is requested. This worked fined as long as I only had two flavor dimensions. As an example, my app.gradle was

…
flavorDimensions "fruit", "color"

productFlavors {

    apple {
        dimension "fruit"
    }
    pear {
        dimension "fruit"
    }

    red {
        dimension "color"
    }
    yellow {
        dimension "color"
    }
}
…

and my src folder was

src/
    appleRed/
    appleYellow/
    pearRed/
    pearYellow/

each one with a custom version of my code. Again, as an example

src/
    appleRed/java/com/example/ExampleFragment.java
    appleYellow/java/com/example/ExampleFragment.java
    pearRed/java/com/example/ExampleFragment.java
    pearYellow/java/com/example/ExampleFragment.java

of course, there is no instance of ExampleFragment in src/main.

At some point during development, I had to include a free and a paid version of the app. I thought that it could be easily achieved by adding a new flavor dimension named version and two product flavors named free and paid:

 …
flavorDimensions "fruit", "color”, “version”

productFlavors {

    apple {
        dimension "fruit"
    }
    pear {
        dimension "fruit"
    }

    red {
        dimension "color"
    }
    yellow {
        dimension "color"
    }

    free {
        dimension "version"
    }
    paid {
        dimension “version”
    }
}
…

but all of a sudden the custom code generated by the combination of fruit and color was not detected by Android Studio anymore. So no appleRed, appleYellow, pearRed nor pearYellow can be used to have custom code and the only way I was able to regain my configuration was to use all the combinations of all the three flavour dimensions:

  src/
      appleRedFree/java/com/example/ExampleFragment.java
      appleRedPaid/java/com/example/ExampleFragment.java
      appleYellowFree/java/com/example/ExampleFragment.java
      appleYellowPaid/java/com/example/ExampleFragment.java
      pearRedFree/java/com/example/ExampleFragment.java
      pearRedPaid/java/com/example/ExampleFragment.java
      pearYellowFree/java/com/example/ExampleFragment.java
      pearYellowPaid/java/com/example/ExampleFragment.java

This is not good because ExampleFragment is duplicated across the same fruitColor* combination (appleRedFree, appleRedPaid have the same ExampleFragment). Same problem happens for resources (the ones in res folder).

My questions are:

1) Is this the expected behaviour from gradle in Android Studio (i.e., not being able to combine a subset of product flavors, following their priority based on their dimension, when having more than two flavour dimensions)?

2) Given the fact that this is the expected behaviour, is there another way I can achieve my customisation without duplicated code or without having a single file with an if-statement inside (e.g., if (BuildConfig.FLAVOR_version == "free") ...) ?

Please note that I’m talking about having custom code which could be complex, so I’m not asking for basic customisation like a build config variable, variant filtering, or something like that.

Ciao answered 4/5, 2016 at 8:18 Comment(0)
V
27

You want to use same extra source directory for some flavors;

appleRedFree + appleRedPaid --> src/appleRed
pearRedFree + pearRedPaid --> src/pearRed
appleYellowFree + appleYellowPaid --> src/appleYellow
pearYellowFree + pearYellowPaid --> src/pearYellow

You can set sourceSet for your flavors:

android {

    // Other stuff here

    flavorDimensions "fruit", "color”, “version”

    productFlavors {

        apple {
            dimension "fruit"
        }
        pear {
            dimension "fruit"
        }

        red {
            dimension "color"
        }
        yellow {
            dimension "color"
        }

        free {
            dimension "version"
        }
        paid {
            dimension “version”
        }
    }

    sourceSets {
        appleRedFree {
            java.srcDirs = ['src/main/java', 'src/appleRed/java']
        }

        appleRedPaid {
            java.srcDirs = ['src/main/java', 'src/appleRed/java']
        }

        appleYellowFree {
            java.srcDirs = ['src/main/java', 'src/appleYellow/java']
        }

        appleYellowPaid {
            java.srcDirs = ['src/main/java', 'src/appleYellow/java']
        }

        pearRedFree {
            java.srcDirs = ['src/main/java', 'src/pearRed/java']
        }

        pearRedPaid {
            java.srcDirs = ['src/main/java', 'src/pearRed/java']
        }

        pearYellowFree {
            java.srcDirs = ['src/main/java', 'src/pearYellow/java']
        }

        pearYellowPaid {
            java.srcDirs = ['src/main/java', 'src/pearYellow/java']
        }
    }

   // Other stuff here
}
Villar answered 15/8, 2016 at 12:57 Comment(5)
Does not work. Gradle project sync fails with error: Error:(46, 0) Could not find method appleRedFree() for arguments [build_5p3mtjx0cpg9la7ms46x7bgzz$_run_closure2$_closure8@5d22a1be] on AndroidSourceSet container. Do you forget to include an apply plugin somewhere?Ciao
@Ciao I've updaded my answer.Villar
Now it works! You also have to use latest (at the time of writing) Gradle Android plugin (2.3.0) and Gradle (3.3). So, in Android Studio, File -> Project Structure -> Project Tab, Gradle Version to 3.3 and Android Plugin Version to 2.3.0Ciao
how to set resvalue for each flavor?Swollen
@roghayehhosseini Just use pearYellowPaid.res.srcDirs += 'src/pearYellow/res`Dymphia
D
4

I have a little enhancement because I really don't like to copy/paste src sets :)

So you can do something like this:

android {
    …
    applicationVariants.all { variant ->
        def flavors = variant.productFlavors
        def fruit = flavors[0].name
        def color = flavors[1].name
        def version = flavors[2].name

        def fruitColorSrcSet = fruit + color.capitalize()
        def srcSet = fruitColorSrcSet + version.capitalize()
        android.sourceSets."$srcSet".java.srcDirs += "src/$fruitColorSrcSet/java"
    }
}

I haven't tested it but it's based on my current implementation with additionally variant.buildType.name usage


Appendix - to set resource directory use

android.sourceSets."$srcSet".res.srcDirs = "src/$fruitColorSrcSet/res"
Dymphia answered 1/7, 2020 at 12:53 Comment(3)
How do you add specific folders depending on the build type? e.g. "appleYellowDebug" - I tried adding variant.buildType.name to the source dir, but then since I'm still using android.sourceSets."$srcSet".java.srcDirs, it gets added to both debug and release builds: android.sourceSets."$srcSet".java.srcDirs += "src/$fruitColorSrcSet" + variant.buildType.name.capitalize() + "/java"Tibbetts
Nevermind, I just managed it as: def buildType = variant.buildType.name def srcSetBuildType = fruitColorSrcSet + version.capitalize() + buildType.capitalize() android.sourceSets."$srcSetBuildType".res.srcDirs += "src/$fruitColorSrcSet" + buildType.capitalize() + "/res"Tibbetts
Hi @JoseGómez, Thanks for the short answer . However I have a scenario where I have a string say "key_one" in src/appleRed/res/values. then I wanted to override in "appRedFree" and then. "appRedFreeDebug". this is leading to duplicate resources.. do you see a way to fix this?. appreciate your help.Convent

© 2022 - 2024 — McMap. All rights reserved.