Android Gradle Flavor values dependent on other flavor dimensions - android

So I've been working on a bit of a white label style project for a while, and I've run into a bit of an issue with extensibility. Specifically, the way the project works, I need to be able to support multiple backend environments, each with unique credentials, on a per-client basis.
I've baked this behavior into the gradle using two flavor dimensions, one for the client and one for the backend, but I'm having a bit of trouble in getting them to gel together as seamlessly as I'd like.
Here's an example of the gradle setup:
productFlavors {
client1 {
applicationId "com.app.client1"
dimension "customer"
resValue "string", "app_name", "Client 1"
}
client2{
applicationId "com.app.client2"
dimension "customer"
resValue "string", "app_name", "Client 2"
}
dev {
ext {
packageExtension = ".DEV"
}
dimension "environment"
resValue "string", "url", "https://dev.hostname.com/"
}
uat {
ext {
packageExtension = ".UAT"
}
dimension "environment"
resValue "string", "url", "https://uat.hostname.com/"
}
stage {
ext {
packageExtension = ".STAGE"
}
dimension "environment"
resValue "string", "url", "https://stg.hostname.com/"
}
prod {
ext {
packageExtension = ""
}
dimension "environment"
resValue "string", "url", "https://hostname.com/"
}
}
This works great for granting access to the right environment and unique code for each client, but it means that I can't include the client ID in the gradle. I have to stick it in a resource file out in the client1Dev directory and the like in order to get the right value at run time.
My question is, is there a way to move the client ID into gradle and get the right value based on the combination of customer and environment flavors?
For example, what I would like to do is something like:
client1 {
applicationId "com.app.client1"
dimension "customer"
resValue "string", "app_name", "Client 1"
dev{
resValue "string", "clientId", "1032145d8eefa00aff0098b08c9d"
}
uat{
resValue "string", "clientId", "8654684561584798531985964/56"
}
}
client2{
applicationId "com.app.client2"
dimension "customer"
resValue "string", "app_name", "Client 2"
dev{
resValue "string", "clientId", "89612ad8967a00aff0098b08c08e"
}
uat{
resValue "string", "clientId", "8612ad890981237409d0ab08c08f"
}
}
Something along these lines that would allow me to store my client ID in the gradle file along with the other backend setup info. I can't find anything about this kind of thing though, and I'm hoping someone on here can shed some light on how to do it, or at least tell me it's impossible.
Thanks!

You could always use separate source trees for the multi-flavor combination and store the Strings directly in the correct resource (since that's essentially what resValue "string" "{ID}" "{VALUE}" is doing anyway).
src/
main/
java/
res/
values/
strings_ids.xml
<string name="clientId">override_me</string>
client1Dev/
client1Uat/
client2Dev/
res/
values/
strings_ids.xml
<string name="clientId">89612ad8967a00aff0098b08c08e</string>
client2Uat/
res/
values/
strings_ids.xml
<string name="clientId">8612ad890981237409d0ab08c08f</string>
Or, if you really want to keep in inside the build.gradle file, you could script it with something (less elegant) along the lines of:
android.applicationVariants.all { variant ->
if (variant.getFlavorName().equalsIgnoreCase("client1Dev")) {
variant.resValue "string", "clientId", "1032145d8eefa00aff0098b08c9d"
}
else if (variant.getFlavorName().equalsIgnoreCase("client1Uat")) {
variant.resValue "string", "clientId", "8654684561584798531985964/56"
}
}
But you can see the latter will get nasty, pretty quickly

Related

Build Single Apk with multiable template/flavors

Is it possible to build apk with more than 1 product flavor?
For example:
I have a Project with 3 flavors (App1 - App2 - App3).
and each app has its own configurations such as applicationId and so on.
and now I want to build different templates (different XML layouts), and the user should be able to switch from layout to another from inside the app.
My problem is the res folder will be huge and it will be hard to maintain, so I'm trying to find a way to separate the different layouts and keep it clean as much as possible.
And if it's possible to do so, then how do I intent or restart the app to the other flavors build?
Another thing I had in mind was to build all XML files in the main res and choose different qualifiers like when we do while creating different screen size (sm - larg - etc.. ) but i couldnt find anyway to add custom qualifiers.
My Gradle code is like this :
flavorDimensions "default"
productFlavors {
demo {
applicationId "test.demo"
versionCode 2
versionName "1.1.2"
resValue "string", "backage_name_file", "test.demo.fileprovider"
resValue "string", "bc", "com.demo"
resValue "string", "bc_e", "extra_data.com.demo"
resValue "string", "default_hostname", "demo.test.com"
resValue "string", "default_username", "demo"
resValue "string", "default_password", "demo"
}
AppOne {
applicationId "test.AppOne"
versionCode 2
versionName "1.1.2"
resValue "string", "backage_name_file", "test.AppOne.fileprovider"
resValue "string", "bc", "com.AppOne"
resValue "string", "bc_e", "extra_data.com.AppOne "
resValue "string", "default_hostname", "AppOne.test.com"
resValue "string", "default_username", "AppOne"
resValue "string", "default_password", "AppOne"
}
AppTwo {
applicationId "test.AppTwo"
versionCode 2
versionName "1.1.2"
resValue "string", "backage_name_file", "test.AppTwo.fileprovider"
resValue "string", "bc", "com.AppTwo"
resValue "string", "bc_e", "extra_data.com.AppTwo"
resValue "string", "default_hostname", "demoAppTwotest.com"
resValue "string", "default_username", "AppTwo"
resValue "string", "default_password", "AppTwo"
}
}
You cannot build an APK with multiple flavours, just as you cannot build one in both debug and release buildTypes. The selected flavour's config / resources gets pulled into the actual APK metadata / manifest, so cannot be modified at runtime.
You will need to include all of your content inside the res folder, but there are a few ways that may help make it easier to manage. I'd recommend the first 3 options, and the 4th if you have a very large number of code + layout files with different behaviour:
Use Fragments to avoid most of your Java / Kotlin code needing to be duplicated.
Include XML layouts instead of redefining everything each time to reuse common elements.
Carefully name your files, for example template1_background, template2_background.
Use multiple modules, one for each "template". You'll then have multiple sensible res folders.
I can see why flavours might have seemed like the solution, but since you need all flavours in one app, this approach is unfortunately not going to work. You'll likely find step #2 will remove almost all of the duplicate files, avoiding the issue entirely!
You can merge resources from different flavours using sourceSets command.
SourceSet Allows you to congifure buildVariants resources folders
for example you can configure your App2 flavour to include App2 res folder and App1 res folder.
Example code:
sourceSets {
App2Debug{
res.srcDirs = ['src/App1/res', 'src/App2/res']
}
}

How to define android:label for different productFlavors in build.gradle

Is there a way to specify different app label (name) for different productFlavors in build.gradle? For example something like this:
productFlavors {
stage {
app name with "-stage" suffix
}
preprod {
app name with "-preprod" suffix
}
prod {
app name without any suffix
}
}
You can achieve it as follows:
Ensure you have specified android:label="#string/app_name" in your AndroidManifest.xml.
Then remove app_name from strings.xml
Change your build.gradle as follows:
productFlavors {
stage {
resValue "string", "app_name", "stage"
}
preprod {
resValue "string", "app_name", "preprod"
}
prod {
resValue "string", "app_name", "prod"
}
}
Step #1: Define a string resource in your main source set to use for your android:label value.
Step #2: In the manifest in your main source set, apply that string resource to the android:label attribute (e.g., android:label="#string/whatever").
Step #3: For any build type, product flavor, or build variant where you want a different value for android:label, create a source set (e.g., app/src/stage/ alongside the existing app/src/main/) and put your desired value in the string resource for that source set (e.g., app/src/stage/res/values/strings.xml with a whatever string resource).
When you build your app, Android will use the build-specific source set for your string resource, falling back to main for situations where you did not override it.
If you definitely want to define it in Gradle — for example, you are generating the label value programmatically at build time — use resValue statements instead of defining the string resources in XML. You would still use that string resource in the manifest, and I would recommend still having a default value in app/src/main/res/values/strings.xml as a fallback.
You can create with resValue "string", "app_name", "label (name) ". Remove app_name from your string.xml file.
like :
productFlavors {
stage {
resValue "string", "app_name", "Stage"
}
preprod {
resValue "string", "app_name", "Preprod"
}
prod {
resValue "string", "app_name", "Prod"
}
}
A friend of mine suggested a very nice solution based on injecting build variables into the manifest. It even allows to use localized app labels. So here it is:
1) Specify android:label in AndroidManifest.xml as follows:
android:label="${appLabel}"
2) Specify a default value in app level build.gradle:
manifestPlaceholders = [appLabel:"#string/appName"]
3) Override the value for needed product flavours:
productFlavors {
stage {
manifestPlaceholders = [appLabel:"#string/appNameStage"]
}
preprod {
manifestPlaceholders = [appLabel:"#string/appNamePreprod"]
}
prod {
// Just let it use a default value
}
}
4) Add string resources which you are referring to (appName, appNameStage, appNamePreprod). Localize them if needed.

How to get a resource value in build.gradle?

The resValue method (or whatever it's called) allows you to set a resource value in buildTypes or productFlavors. Is there a corresponding way to get a resource value that was set by resValue?
It appears that productFlavors is evaluated before buildTypes, so a resValue set in buildTypes takes precedence. I want to append "Debug" to the app name in debug builds, but I need to get the value that was set in the product flavor in order to append to it.
Edit: I tried Marcin Koziński's suggestion to use a variable, but all product flavors are evaluated before any build type. Therefore, this does not work:
android {
String appName = ""
productFlavors {
Foo {
appName = "Foo"
}
Bar {
appName = "Bar"
}
}
buildTypes {
release {
resValue "string", "app_name", appName
}
debug {
resValue "string", "app_name", appName + " Debug"
}
}
}
In buildTypes, appName always has the value from the last product flavor. So in this example, all builds receive the name "Bar" or "Bar Debug".
Basically, I need a resValueSuffix analogous to applicationIdSuffix. Apparently no such animal exists. Does the com.android.application plugin expose anything that I could use to achieve this?
If you are only trying to set the App Label (or other manifest values) you can solve this with manifest placeholders.
android {
productFlavors {
Foo {
applicationId "com.myexample.foo"
manifestPlaceholders.appName = "Foo"
}
Bar {
applicationId "com.myexample.bar"
manifestPlaceholders.appName = "Bar"
}
}
buildTypes {
release {
manifestPlaceholders.appNameSuffix =""
}
debug {
manifestPlaceholders.appNameSuffix =".Debug"
applicationIdSuffix ".debug"
}
}
}
Then in your Android Manifest you simply use both placeholders for your app name (or other values)
<application
android:label="${appName}${appNameSuffix}"
...
</application>
This allow you to install all 4 variants side by side on a single device as well as give them different names in the app drawer / launcher.
EDIT 11/22/2019
Updated how placeholders values are set based on feedback from #javaxian
You can check the build variants like this
Define values in gradle
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);
To have an alternative version of a resource in debug builds you can use the debug source set.
strings.xml can be found under following path src/main/res/values, which means it's in the main source set. If you create a new directory src/debug/res/values you can put a new strings.xml file in there with values that should be overridden in debug builds. For example:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">My Application Debug</string>
</resources>
This will replace whatever value app_name has in your main file. You don't have to duplicate all the strings in here - ones you don't include here are simply taken from the main file.

Defining 'resValue' using an existing string definition

I'm interested in defining my many flavors of my apps more so in the strings.xml files rather than the build.gradle. Given multiple flavors, I'd like a simplified release/debug variant:
buildTypes {
release {
signingConfig signingConfigs.release
resValue "string", "app_name", "#string/prod_name"
}
debug {
applicationIdSuffix ".beta"
resValue "string", "app_name", "#string/beta_name"
}
Then in each of my build flavors' custom res/values/strings.xml files, I would define each their own "prod_name" and "beta_name". I also want to use this similar framework for defining providers' authorities etc...
This currently will build fine via gradle command-line, but fails to be recognized by Android Studio.
Android Studio Error:
I find this within 'generated.xml'
<!-- Values from build type: debug -->
<string name="app_name">#string/beta_name</string>
Which is typical of how one string references another. But this time Android Studio gives me this error:
Error:(7, 29) No resource found that matches the given name
(at 'app_name' with value '#string/beta_name').
I'm using Android Studio 2.1 Preview 5
This is actually possible. All you have to do is declare those strings as empty in your defaultConfig, like this:
defaultConfig {
resValue "string", "prod_name", ""
resValue "string", "beta_name", ""
}
In my experience you can't resolve a #string/my_string in the resValue DSL. Gradle put the value as a simple string inside the resource file.
In this case you can use different folder to achieve it:
Just use:
src/release/res/values/strings.xml
src/debug/res/values/strings.xml
If you would like to use different resource for each build variant (build type+flavor) you can use:
src/flavor1Release/res/values/strings.xml
src/flavor1Debug/res/values/strings.xml
src/flavor2Release/res/values/strings.xml
src/flavor2Debug/res/values/strings.xml
It is possible you can add res values inside the gradle for your build flavors
productFlavors {
prod {
dimension "default"
resValue "string", "app_name", "your prod app name"
}
staging {
dimension "default"
resValue "string", "app_name", "your staging app name"
}
}

Build only default (no flavors/variants)

I have a bunch of product flavors defined for my build. However in some scenarios I want to build without a flavor.
However when I try to build a release with no flavor, ie
gradlew assembleRelease
It goes through and builds all of the variants, which takes a really long time. I would like to kick off a release build and ignore all flavors. Is that possible?
I am sure I could add an empty flavor called 'default, and build that. Seems like I should not have to do that.
EDIT:
defaultConfig {
resValue "string", "hello", "Hello default"
}
productFlavors {
foo {
resValue "string", "hello", "Hello Foo"
}
bar {
resValue "string", "hello", "Hello Bar"
}
}
Seems the answer at the moment is to provide your own 'default' flavor
productFlavors {
defaults {
// yup its empty cause I just want to build with the defaults
// that are already defined.
}
foo {
resValue "string", "hello", "Hello Foo"
}
bar {
resValue "string", "hello", "Hello Bar"
}
}
I found out that "main" as a flavor works. So i dont need to add an additional folder with google-services.json or anything in it
productFlavors {
main{
}
flav1 {
applicationId 'id'
}
}

Categories

Resources