Different app names for different build flavors?
AndroidGradleAndroid Problem Overview
I have 2 build flavors, say, flavor1 and flavor2.
I would like my application to be named, say, "AppFlavor1" when I build for flavor1 and "AppFlavor2" when I build for flavor 2.
It is not the title of activities I want to change. I want to change the app name as it's displayed on the phone menu and elsewhere.
From build.gradle
I can set various parameters for my flavors but, it seems, not the app label. And I can not change the app label programmatically based on some variable, too.
So, how do people handle this?
Android Solutions
Solution 1 - Android
Remove app_name
from strings.xml
(else gradle will complain of duplicate resources). Then modify build file like this:
productFlavors {
flavor1{
resValue "string", "app_name", "AppNameFlavor1"
}
flavor2{
resValue "string", "app_name", "AppNameFlavor2"
}
}
Also make sure @string/app_name
value is assigned for android:label
attribute in the manifest.
<application
...
android:label="@string/app_name"
...
This is less disruptive than creating new strings.xml
under different built sets or writing custom scripts.
Solution 2 - Android
Instead of changing your main strings.xml with a script and risk messing up your source control, why not rely on the standard merging behavior of the Android Gradle build?
My build.gradle
contains
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
release {
res.srcDir 'variants/release/res'
}
debug {
res.srcDir 'variants/debug/res'
}
}
So now I can define my app_name
string in the variants/[release|debug]/res/strings.xml
. And anything else I want to change, too!
Solution 3 - Android
If you want to maintain localization for the app name in different flavors, then you can achieve it as follows:
-
Specify
android:label
in<application>
available inAndroidManifest.xml
as follows: -
Specify default value fo
appLabel
in app levelbuild.gradle
:
manifestPlaceholders = [appLabel:"@string/defaultName"]
-
Override the value for product flavors as follows:
productFlavors { AppFlavor1 { manifestPlaceholders = [appLabel:"@string/flavor1"] } AppFlavor2 { manifestPlaceholders = [appLabel:"@string/flavor2"] }
}
-
Add string resources for each of Strings (defaultName, flavor1, flavor2) in your
strings.xml
. This will allow you to localize them.
Solution 4 - Android
You can add a strings resource file to each flavor, then use those resource files to change your app name.
For example in one of my apps, I have a free and paid version. To rename them "Lite" and "Pro", I created a meta_data.xml
file, and added my app_name
value to that XML and removed it from strings.xml
.
Next, in app/src
create a folder for each flavor (see below for example structure). Inside these directories, add res/values/<string resource file name>
. Now, when you build, this file will be copied into your build and your app will be renamed.
File structure:
app/src
/pro/res/values/meta_data.xml
/lite/res/values/meta_data.xml
Solution 5 - Android
Another option that I actually use is change the manifest for each application. Instead of copy the resource folder, you can create a manifest for each flavour.
sourceSets {
main {
}
release {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
debug {
manifest.srcFile 'src/debug/AndroidManifest.xml'
}
}
You have to have a principal AndroidManifest in your src main that will be the principal. Then you can define a manifest with only some options for each flavour like (src/release/AndroidManifest.xml):
<manifest package="com.application.yourapp">
<application android:icon="@drawable/ic_launcher">
</application>
</manifest>
For debug, AndroidManifest (src/debug/AndroidManifest.xml):
<manifest package="com.application.yourapp">
<application android:icon="@drawable/ic_launcher2">
</application>
</manifest>
Compiler will do a merge of the manifest and you can have a icon for each flavour.
Solution 6 - Android
This can easily be accomplished under buildTypes
buildTypes {
debug {
buildConfigField("String", "server_type", "\"TEST\"")
resValue "string", "app_name", "Eventful-Test"
debuggable true
signingConfig signingConfigs.debug_key_sign
}
stage {
buildConfigField("String", "server_type", "\"STAGE\"")
resValue "string", "app_name", "Eventful-Stage"
debuggable true
signingConfig signingConfigs.debug_key_sign
}
release {
buildConfigField("String", "server_type", "\"PROD\"")
resValue "string", "app_name", "Eventful"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//TODO - add release signing
}
}
Just make sure to remove app_name from strings.xml
Solution 7 - Android
First of all, answer this question: "Can the user install both flavors of your application on the same device?"
I use a Python script that patches the source. It contains some reusable functions and, of course, knowledge what needs be patched in this particular project. So the script is application-specific.
There is a lot of patching, the data for patching are kept in a Python dictionary (including application package names, they BTW are different from the Java package name), one dictionary per flavor.
As to l10n, strings may point to other strings, e.g. in my code I have:
<string name="app_name">@string/x_app_name_xyz</string>
<string name="x_app_name_default">My Application</string>
<string name="x_app_name_xyz">My App</string>
Solution 8 - Android
How do I make string/app_name different per flavor though?
I wanted to write an update, but realized that it is bigger than the original answer saying that I use a Python script that patches the source.
The Python script has a parameter, a directory name. That directory contains per-flavor assets, resources like launcher icons, and the file properties.txt with a Python dictionary.
{ 'someBoolean' : True
, 'someParam' : 'none'
, 'appTitle' : '@string/x_app_name_xyz'
}
The Python script loads the dictionary from that file and replaces the value between <string name="app_name">
and </string>
by the value of properties['appTitle']
.
The below code is provided on the as-is/as-was basis etc.
for strings_xml in glob.glob("res/values*/strings.xml"):
fileReplace(strings_xml,'<string name="app_name">',properties['appTitle'],'</string>',oldtextpattern=r"[a-zA-Z0-9_/@\- ]+")
to read properties from one or more such file:
with open(filename1) as f:
properties = eval(f.read())
with open(filename2) as f:
properties.update(eval(f.read()))
and the fileReplace function is:
really = True
#False for debugging
# In the file 'fname',
# find the text matching "before oldtext after" (all occurrences) and
# replace 'oldtext' with 'newtext' (all occurrences).
# If 'mandatory' is true, raise an exception if no replacements were made.
def fileReplace(fname,before,newtext,after,oldtextpattern=r"[\w.]+",mandatory=True):
with open(fname, 'r+') as f:
read_data = f.read()
pattern = r"("+re.escape(before)+r")"+oldtextpattern+"("+re.escape(after)+r")"
replacement = r"\g<1>"+newtext+r"\g<2>"
new_data,replacements_made = re.subn(pattern,replacement,read_data,flags=re.MULTILINE)
if replacements_made and really:
f.seek(0)
f.truncate()
f.write(new_data)
if verbose:
print "patching ",fname," (",replacements_made," occurrence" + ("s" if 1!=replacements_made else ""),")",newtext,("-- no changes" if new_data==read_data else "-- ***CHANGED***")
elif replacements_made:
print fname,":"
print new_data
elif mandatory:
raise Exception("cannot patch the file: "+fname+" with ["+newtext+"] instead of '"+before+"{"+oldtextpattern+"}"+after+"'")
The first lines of the script are:
#!/usr/bin/python
# coding: utf-8
import sys
import os
import re
import os.path
import shutil
import argparse
import string
import glob
from myutils import copytreeover
Solution 9 - Android
In AndroidManifest file, in the application tag you have this line:
android:label
And there you can say how to application label will appear in applications menu on device