Build Single Apk with multiable template/flavors - android

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']
}
}

Related

Android Gradle Flavor values dependent on other flavor dimensions

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

How to give priority to strings.xml defined by flavor over strings.xml defined by a library?

I have an Android app with multiple flavors, each flavor defines the app_name string on its own strings.xml file. Recently I incorporated a new library that defines its own value for app_name at its own strings.xml file. The problem is that the final value for app_name in the merged resources is the app_name value defined by the library. How I can bring priority to the flavor app_name value?
Currently I'm define specific values for app_name in the individual strings.xml file per flavor.
I expect that the app_name value was the flavor value and not the library value.
UPDATE:
My flavors are defined as below:
productFlavors {
doctoronline {
dimension "client"
applicationId 'com.doctoronline.doctoronline'
resValue 'string', 'filebrowser_provider', 'com.doctoronline.doctoronline.aditya.fileprovider'
buildConfigField 'String', 'CORPORATE_DOMAIN', '"e4c4aa0f523941d2a332d15101f12e9e"'
buildConfigField 'String', 'SHORT_NAME', '"DRONLINE"'
buildConfigField 'String', 'WAITING_MESSAGE', '"DRONLINE_WAITING_MESSAGE_PATIENT_DEFAULT"'
}
mapfre {
dimension "client"
applicationId 'com.doctoronline.mapfre'
resValue 'string', 'filebrowser_provider', 'com.doctoronline.mapfre.aditya.fileprovider'
buildConfigField 'String', 'CORPORATE_DOMAIN', '"3282a15f144e288bac4c07b1598e9234"'
buildConfigField 'String', 'SHORT_NAME', '"mapfre"'
buildConfigField 'String', 'WAITING_MESSAGE', '"MAPFRE_WAITING_MESSAGE_PATIENT_DEFAULT"'
}
...
//4 or 5 extra flavors
}
The library that I'm adding is this.
If you want to change your app name based on product flavor you build, you have two options:
1. using resValue. this helps you to add some resources to your app like string resource, color resource.
2. using manifestPlaceHolder. this helps you to add variables to android manifest file from your build.gradle file and change these variables through build types or product flavors.
this is how to add resValue to your product flavor
productFlavors {
flavor1 {
dimension = 'test'
resValue("string", "app_name", "MY APP NAME")
}
flavor2 {
dimension = 'test'
resValue("string", "app_name", "MY APP NAME")
}
}
if your strings.xml has string with name app_name you have to remove it to avoid duplication res values message.
to check if app name changed through different product flavors change your product falvor and build the project then check what values appear in application tag in the manifest file.
The second way is using manifestPlaceHolder. in your product flavor add manifestPlaceHolder and then use this variable inside your application tag in the manifest file
productFlavors {
flavor1 {
dimension = 'test'
manifestPlaceholders=[appName: 'MY APP NAME']
}
flavor2 {
dimension = 'test'
manifestPlaceholders=[appName: 'MY APP NAME']
}
}
then navigate to your android manifest file
<application
android:name=".DemoApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="${appName}"
android:supportsRtl="true"
android:theme="#style/AppTheme">
...
</application>
Hope this answer is helpful. Happy coding.

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 add text translation via resValue

On our Android app we're passing strings resources via build script using the resValue, something like this:
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
resValue "string", "foo", "foo_value"
resValue "string", "bar", "bar_value"
... etc
How to add translated version of those strings. To go on values-de, values-ch, values-sp, etc folders?
Apparently it is not possible to do it from the gradle file as mentioned here. If you just want to use the values, better use the buildConfigField instead

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"
}
}

Categories

Resources