I have an android app that I want customized for different clients, usually changed colors.
I could use flavors, but
The problem is that every app should have 2 stages (so, 2 apps for every client), 1 for QA and 1 for Prod and they use different rest-api urls, custom for each client.
What would be ideal for me is a way to nest flavors so I can define properties for each,but it's not supported. This will also allow me to modify some layouts for client_1 and use the rest from main, which I also need to do. Example:
productFlavors {
client_1 {
dev {
buildConfigField "String", "API_URL", "https://...."
}
prod {
buildConfigField "String", "API_URL", "https://...."
}
}
client_2 {
dev { //...
}
prod {
//...
}
}
}
Any idea how I can achieve that?
The other option would be to have a common library app and have different apps for different clients with their config, but that would make it more complex.
In my project i used , build types with product flavours. I will explain it here.
My project looks like this,
and add this lines in the gradle file,
buildTypes {
prod {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
dev {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
client_1 {
applicationId "com.example.client_1"
versionCode 1
}
client_2 {
applicationId "com.example.client_2"
versionCode 1
}
}
When the app is based on more than one criteria, instead of creating a lot of flavors you can define flavor dimensions.
The flavor dimensions define the cartesian product that will be used to produce variants.
Example:
flavorDimensions("dimA", "dimB")
productFlavors {
row1 {
...
dimension = "dimA"
}
row2 {
...
dimension = "dimA"
}
row3 {
...
dimension = "dimA"
}
col1 {
...
dimension = "dimB"
}
col2 {
...
dimension = "dimB"
}
col3 {
...
dimension = "dimB"
}
}
This config will produce 18 (3*3*2) variants (if you have the 2 standard build types : debug and release).
The following build variants will be created:
row1-col1-debug
row1-col2-debug
row1-col3-debug
row1-col1-release
row1-col2-release
row1-col3-release
row2-col1-debug
row2-col2-debug
row2-col3-debug
row2-col1-release
row2-col2-release
row2-col3-release
row3-col1-debug
row3-col2-debug
row3-col3-debug
row3-col1-release
row3-col2-release
row3-col3-release
Related
In my application I have 4 environments i.e Dev, SIT, UAT, Release. For each environment I have a build type with it's own configuration. Much like the suggested way, e.g:
buildTypes {
Dev {
buildConfigField "String", 'BASE_URL', "https://dev-api.yourbackend.com"
}
SIT {
buildConfigField "String", 'BASE_URL', "https://sit-api.yourbackend.com"
}
UAT {
buildConfigField "String", 'BASE_URL', "https://uat-api.yourbackend.com"
}
Release {
buildConfigField "String", 'BASE_URL', "https://api.yourbackend.com"
}
}
But now I have 5-6 different such configurations, not just the BASE_URL. Like ClientId, ClientSecret etc..
Plus I have 2 product flavours, meaning I need to create 8 different build variants and have all these 5-6 configurations inside each such variant. All that looks clumsy.
1 other solution I found is to create a folder for each of the build variant like flavour1Dev, flavour2Sit, flavour2Uat etc.. and have the config file specific to that variant inside that folder. This again involves 8 different folders and keeps growing as we add more environments.
Is there a better way of handling this, probably like a config.gradle file where I can only have these configurations and import it back in the app/build.gradle, a way of modularising the whole build variant configurations.
Update-1
I tried this as per #Md. Asaduzzaman's Answer:
build.gradle
…
…
apply from: './config.gradle'
…
…
android {
…
…
defaultConfig {
…
…
buildConfigField('String', ‘BASE_URL’, '"https://default.yourbackend.com”')
}
…
…
}
config.gradle
android {
buildTypes {
dev {
buildConfigField('String', ‘BASE_URL’, '"https://dev.yourbackend.com”')
}
sit {
buildConfigField('String', ‘BASE_URL’, '"https://sit.yourbackend.com”')
}
}
}
The issue is that in the generated BuildConfig, I still see the default values:
public static final String BASE_URL = "https://default.yourbackend.com";
You already mentioned the solution yourself by creating separate config.gradle file. I try to implement it like below:
Step - 1: Create config.gradle inside your app folder and add all the configurations.
android {
buildTypes {
Dev {
buildConfigField "String", 'BASE_URL', "https://dev-api.yourbackend.com"
}
SIT {
buildConfigField "String", 'BASE_URL', "https://sit-api.yourbackend.com"
}
UAT {
buildConfigField "String", 'BASE_URL', "https://uat-api.yourbackend.com"
}
Release {
buildConfigField "String", 'BASE_URL', "https://api.yourbackend.com"
}
}
productFlavors {
flavor1 {
/*implementation*/
}
flavor2 {
/*implementation*/
}
flavor3 {
/*implementation*/
}
}
}
Step -2: Include the config.gradle in your app/build.gradle.
apply plugin: 'com.android.application'
apply from: './config.gradle'
....
I don't figure out how can I set a resource value (resValue) in my build.gradle, depending on the build variant selection.
Here a bit of explanation.
I'm working with the Skype For Business SDK (hereafter referred to as Sfb) and during is implementation, it ask me to add a resource value named ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE.
So I was looking in their application example (available here) and found that in build.gradle they have added as follow the corresponding value :
android {
...
defaultConfig {
applicationId "com.microsoft.office.sfb.sfbdemo"
...
resValue ("string", "ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE", "${applicationId}"
}
...
}
This value then used in a SfbSDK class, checking if it match the application package name.
And here is my trouble, I work with different flavorDimensions as describe in my build.gradle below.
apply plugin: 'com.android.application'
...
android {
...
defaultConfig {
applicationId "com.tsp.test"
...
resValue ("string", "ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE", "${applicationId}"
}
...
flavorDimensions("customer", "version")
productFlavors {
a {
dimension "customer"
applicationIdSuffix ".a"
}
b {
dimension "customer"
applicationIdSuffix ".b"
}
alpha {
dimension "version"
applicationIdSuffix ".alpha"
}
beta {
dimension "version"
applicationIdSuffix ".beta"
}
release {
dimension "version"
applicationIdSuffix ".release"
}
}
}
...
Depending on my build variant selection, this will generate me 6 different APK :
com.tsp.test.a.alpha ; com.tsp.test.a.beta ; com.tsp.test.a.release
com.tsp.test.b.alpha ; com.tsp.test.b.beta ; com.tsp.test.b.release
So when the match checking is does, my application crash with the message error
Caused by: java.lang.RuntimeException: ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE string not set to applicationId
at com.microsoft.office.sfb.appsdk.Application.initialize(Application.java:110)
at com.microsoft.office.sfb.appsdk.Application.getInstance(Application.java:144)
at com.tsp.test.RootActivity.onCreate(RootActivity.java:89)
Of course, because com.tsp.test doesn't match com.tsp.test.a.alpha (or any other APK).
How can I achieve a dynamic resValue depending on the build variant selected that match the right application package name ?
EDIT :
To explain a bit more. First I choose the Build Variants as follow :
Customer : A
Version : Alpha
Then, in my RootActivity#onCreate() (my launcher activity), I start to configure the SfbSDK with an application instance depending on the SfbSDK :
this.mApplication = com.microsoft.office.sfb.appsdk.Application.getInstance(this.getApplication().getApplicationContext());
Somewhere in getInstance() method, the SfbSDK do an equals() between the context.getPackageName() and context.getString(string.ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE);
So, for debug, in my RootActivity#onCreate() I just wrote this two lines
String whatIWant = this.getPackageName(); // give me *com.tsp.test.a.alpha*
String whatIGet = this.getString(R.string.ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE); // give me *com.tsp.test*
This doesn't match ! So in SfbSDK the condition wasn't passed.
Well, thanks to Radesh and this link he provided to me, I've found my solution.
I removed resValue from the defaultConfig block then I added a new block in the android plugin task, that create the resValue for each variant.
apply plugin: 'com.android.application'
...
android {
...
defaultConfig {
applicationId "com.tsp.test"
...
}
...
flavorDimensions("customer", "version")
productFlavors {
a {
dimension "customer"
applicationIdSuffix ".a"
}
b {
dimension "customer"
applicationIdSuffix ".b"
}
alpha {
dimension "version"
applicationIdSuffix ".alpha"
}
beta {
dimension "version"
applicationIdSuffix ".beta"
}
release {
dimension "version"
applicationIdSuffix ".release"
}
}
...
applicationVariants.all { variant ->
variant.resValue "string", "ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE", "\"${applicationId}\""
}
}
...
This generate correctly the resValue with the package name of the selected variant.
For variant customer A and version Alpha, I get resValue = com.tsp.test.a.alpha.
you must declear variable ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE in gradle.properties like below
//default
org.gradle.jvmargs=-Xmx1536m
ENTERPRISE_AUTHENTICATOR_ACCOUNT_TYPE=xxx
Is there a way to group gradle properties to be reused? I can accomplish the following by having duplicate properties but I am trying to avoid this by grouping them in a common place. I have the following build types
buildTypes {
debug {}
qa {
applicationIdSuffix = ".qa" //Duplicate
}
qa2 {
applicationIdSuffix = ".qa" //Duplicate
}
release {}
}
flavors {
flagship {}
other1 {}
other2 {}
}
I already have different flavors defined so I don't think I can place the common properties in different flavors. I was hoping I'd be able to do something like
def commonQaProps {
applicationIdSuffix = ".qa"
//Other properties
}
and then have
qa { commonQaProps }
qa2 { commonQaProps }
Is something like this possible?
After reading the documentation if you're building one buildType off of another you can use initWith.
buildTypes {
debug {}
qa {
applicationIdSuffix = ".qa" //Duplicate
}
qa2 {
initWith qa
//Customize other properties here
}
release {}
}
I still would like to know if grouping properties is possible to use in many variants that don't inherit from each other.
Is there any way to set different versionNameSuffix for various product flavors in a way it is possible to do this for build types?
The following code works for me:
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
verstionNameSuffix "-prod"
}
}
But when I want to set versionNameSuffix for product flavors like this:
productFlavors {
production {
versionNameSuffix "-prod"
}
development {
versionNameSuffix "-dev"
}
}
I get this error:
Error:(48, 0) Could not find method versionNameSuffix() for arguments [-dev] on ProductFlavor_Decorated{name=development, dimension=null, minSdkVersion=null, targetSdkVersion=null, renderscriptTargetApi=null, renderscriptSupportModeEnabled=null, renderscriptNdkModeEnabled=null, versionCode=null, versionName=null, applicationId=null, testApplicationId=null, testInstrumentationRunner=null, testInstrumentationRunnerArguments={}, testHandleProfiling=null, testFunctionalTest=null, signingConfig=null, resConfig=null, mBuildConfigFields={BASE_URL=com.android.builder.internal.ClassFieldImpl#1630a8db}, mResValues={}, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}} of type com.android.build.gradle.internal.dsl.ProductFlavor.
Is there any other way to set the versionNameSuffix for the product flavor?
productFlavors now support versionNameSuffix
From the android developer docs
https://developer.android.com/studio/build/build-variants#flavor-dimensions
android {
...
buildTypes {
debug {...}
release {...}
}
// Specifies the flavor dimensions you want to use. The order in which you
// list each dimension determines its priority, from highest to lowest,
// when Gradle merges variant sources and configurations. You must assign
// each product flavor you configure to one of the flavor dimensions.
flavorDimensions "api", "mode"
productFlavors {
demo {
// Assigns this product flavor to the "mode" flavor dimension.
dimension "mode"
...
}
full {
dimension "mode"
...
}
// Configurations in the "api" product flavors override those in "mode"
// flavors and the defaultConfig block. Gradle determines the priority
// between flavor dimensions based on the order in which they appear next
// to the flavorDimensions property above--the first dimension has a higher
// priority than the second, and so on.
minApi24 {
dimension "api"
minSdkVersion 24
// To ensure the target device receives the version of the app with
// the highest compatible API level, assign version codes in increasing
// value with API level. To learn more about assigning version codes to
// support app updates and uploading to Google Play, read Multiple APK Support
versionCode 30000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi24"
...
}
minApi23 {
dimension "api"
minSdkVersion 23
versionCode 20000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi23"
...
}
minApi21 {
dimension "api"
minSdkVersion 21
versionCode 10000 + android.defaultConfig.versionCode
versionNameSuffix "-minApi21"
...
}
}
}
...
You can also operate on productFlavors like this:
android.productFlavors.all { flavor ->
flavor.versionNameSuffix = "-${flavor.name}"
}
There is no direct way of doing so. Currently, com.android.application gradle-plugin does not support any DSL for productFlavors to have versionNameSuffix.
If you want to do this, you have to create two methods.
import java.util.regex.Matcher
import java.util.regex.Pattern
def getCurrentVersionSuffix() {
def currentFlavor = getCurrentFlavor()
if (currentFlavor.equals("prod")) {
return "-prod"
} else if (currentFlavor.equals("uat")) {
return "-uat"
} else if (currentFlavor.equals("dev")) {
return "-dev"
}
}
def getCurrentFlavor() {
String taskRequestName = getGradle().getStartParameter().getTaskRequests().toString()
Pattern pattern;
if (taskRequestName.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(taskRequestName)
if (matcher.find()) {
return matcher.group(1).toLowerCase()
} else {
return "";
}
}
and update the buildTypes DSL
buildTypes {
debug {
versionNameSuffix getCurrentVersionSuffix()
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
versionNameSuffix getCurrentVersionSuffix()
}
}
This is the only way to achieve right now. May be in future, they could provide some DSL or something.
I have written a blog on this (http://www.pcsalt.com/android/product-flavors-android/) and the code is pushed on GitHub (https://github.com/krrishnaaaa/product-flavours/blob/master/app/build.gradle).
We have 2 productFlavors (testServer, liveServer) and 2 build types (debug, release).
Due to existing API keys, I have to append package names based on buildType + productFlavor.
For example something like:
buildTypes {
debug {
applicationIdSuffix '.dbg' + (testServer ? '.test' : '.live')
}
release {
applicationidSuffix '' + (testServer ? '.test')
}
}
is this possible? how?
productFlavors {
testServer {
applicationId = "com.example.my.pkg.test"
}
liveServer {
applicationId = "com.example.my.pkg.live"
}
}
buildTypes {
debug {
applicationIdSuffix ".debug"
}
}
For more information, take a look at the Android documentation regarding application ids.