Separate source files per build variant in Gradle (Android)? - 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.

Related

Build type specific folder not recognized for a product flavor

I have configured my project to have multiple product flavors. I have the following code in my module build.gradle:
android {
// Rest of the configuration omitted for clarity
buildTypes {
debug {
// ...
}
release {
// ...
}
}
productFlavors {
paid
free
}
}
If I create a file (any file: Java, resources, ...), in the paid, free, debug or release, it is recognized by Android Studio and I can use it in my project.
However, if the same file is created in paidDebug (or a similar folder) it is not recognized in Android Studio. Do I need any extra configuration for this to work? Is this not supported (yet)?
Source code files with the same name should be placed in all of the alternative source sets (but not in the 'main').
Either:
app/src/free/java/com/domain/myapp/MainActivity.java
app/src/paid/java/com/domain/myapp/MainActivity.java
Or:
app/src/freeDebug/java/com/domain/myapp/MainActivity.java
app/src/freeRelease/java/com/domain/myapp/MainActivity.java
app/src/paidDebug/java/com/domain/myapp/MainActivity.java
app/src/paidRelease/java/com/domain/myapp/MainActivity.java
Resource files with the same name can be placed in any source set, including 'main'.
app/src/main/res/layout/activity_main.xml
app/src/paidDebug/res/layout/activity_main.xml
app/src/paidRelease/res/layout/activity_main.xml
In this case, when building the 'free' flavor, the layout file from 'main' set will be used. But, during the build of the 'paid' flavor, the specific version of the layout will be used.
You can specify specific source directories in your build.gradle file. For example, to add testFreeRelease to unit test sources and testFree to android integration test sources:
android {
[...]
sourceSets {
main.java.srcDirs += 'src/main/java'
testFreeRelease.java.srcDirs += 'src/testFreeRelease/java'
androidTestFree.java.srcDirs += 'src/androidTestFree/java'
}
}
This also works for Kotlin. Just switch java with kotlin.

While using product flavours what files are common in each flavour and what files are specific to that flavour?

productFlavors {
India {
}
USA {
}
}
Lets take 2 product flavours for an example
1. India
2. USA
total number of build variants will be 4
1. IndiaDebug
2. IndiaRelease
3. USADebug
4. USARelease
Which files are common for all flavours and which files are flavour specific as well as debug and release specific ?
If localization is supported and if english is common language for India and USA then each build will have separate english file or common file ?
Product Flavour is a awesome solution to build different varieties of the same application with individual features.
Specific Files
Say like , one of your Activity will have different functionality and UI, then you can avoid keeping that Activity in common package and move to respective flavour. Each flavour can have separate java and res folder along with Manifest (which is not mandatory, Studio take care of itself). It is here your specific Activity's java file and xml file should be placed.
Example : Login Screen will have different UI and features in each flavour
Now during runtime as well as compile time, Android Studio switches between the packages and picks suitable files. This is done through Build Variant feature
Common Files
So coming to common files which is applicable is all the flavours, let it be in main/java and main/res itself.
Ideally depending on your flavour numbers, bundle.gradle will look similar to this.
productFlavors {
student {
applicationId "com.abc.student"
}
staff {
applicationId "com.abc.staff"
}
tempstaff {
applicationId "com.abc.tempstaff"
}
}
sourceSets {
tempstaff {
manifest.srcFile 'src/tempstaff/AndroidManifest.xml'
}
student{
manifest.srcFile 'src/student/AndroidManifest.xml'
}
staff {
manifest.srcFile 'src/staff/AndroidManifest.xml'
}
}
Now to conclude the answer, files which are common throughout the
application will remain in the main package. Specific files
applicable to separate flavour will go in that flavour. This means
flavours can have extra Activity/Features that are not at all a part
of others including main also
Go through this link for more information.

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

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