I am creating an android project with different product flavours. My project structure is like this
Flavor1
Flavor2
Main
where Flavor1 contains a Constants.java file, Flavor2 also contains a Constants.java file but it is not in main. My main package contains another class Controller that uses variables defined in Constants. When I try to run my project it throws an error 'Cannot resolve symbol Constants'.
When I press alt+enter it imports the file from selected flavor and the error is gone but when I switch to other flavor it doesn't identify the import because it's from flavor1.
Here is code from my build.gradle file than defines product flavors and sourcesets.
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
applicationIdSuffix ".debug"
}
}
productFlavors {
Flavor1{
applicationId = "app.com.Flavor1
}
Flavor2{
applicationId = "app.com.Flavor2
}
}
sourceSets {
main{
java {
srcDirs('src/Flavor1/java/src', 'src/Flavor2/java/src','src/main/java/src')
}
res {
srcDirs('src/Flavor1/res/src', 'src/Flavor2/res/src','src/main/res/src')
}
}
Flavor1{
java {
srcDirs('src/Flavor1/java/src', 'src/main/java/src')
}
res {
srcDirs('src/Flavor1/res/src', 'src/main/res/src')
}
}
Flavor2{
java {
srcDirs('src/Flavor2/java/src', 'src/main/java/src')
}
res {
srcDirs('src/Flavor2/res/src', 'src/main/res/src')
}
}
}
I want every flavor to have its own Constants File. How this can be achieved? How can I use Constants file of every flavor in main so that it imports or uses file of selected Build Variant?
Any help will be appreciated.
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 have an issue with adding a cpp folder and CMakelist.txt file to a specific flavor in android studio.
I was working on three separate apps with a unique database structure, so I tried to make these three apps as one application using build flavors and other related settings.
For the first two apps, it did so well, but for the last one, I faced an issue that I had no idea how to add cpp folder and CMakelist.txt file to that specific flavor using Gradle. As you may have guessed, the last application is NDK based and uses a CMakelist.txt file and has an activity that works with JNI.
android {
...
defaultConfig {
applicationId "com.example"
...
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
flavorDimensions 'program'
productFlavors {
first {
dimension = 'program'
applicationIdSuffix = '.first'
}
second {
dimension = 'program'
applicationIdSuffix = '.second'
}
third {
dimension = 'program'
applicationIdSuffix = '.third'
externalNativeBuild {
cmake {
relativeProjectPath "src/third/cpp/CMakeLists.txt"
version "3.10.2"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
ndk { abiFilters "arm64-v8a" }
}
debug {
ndk { abiFilters "arm64-v8a" }
}
}
}
}
I am using a relativeProjectPath method in gradle, and expect that CMakelist.txt links with my cpp folders but nothing happens.
I have not tried it that way. What worked for me in my previous projects is this.
android {
...
flavorDimensions "program"
productFlavors {
...
third {
dimension "program"
externalNativeBuild.cmake {
arguments "-DFLAVOR=THIRD_PROGRAM"
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
...
}
Then in your CMakeList.txt, do this conditional test
if(${FLAVOR} STREQUAL "THIRD_PROGRAM")
// build your cpp codes for flavor THIRD
else()
// nothing to build?
endif()
Note that my CMakeList.txt is in app/ directory.
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
I have a project which has multiple localizations.
Now I have to replace a certain word in all strings.xml files for different buildTypes. For example let's say I have this:
<string>My name is Bill</string>
<string>Bill is on duty today</string>
And in another buildType I need to have
<string>My name is Will</string>
<string>Will is on duty today</string>
How can I do it (probably through Gradle)?
You can do something like following in build.gradle but would only apply to that particular string resource.
resValue "string", "<string_name>", string_value
Ok, found a correct solution, which is not a workaround:
For the required buildType add the following
applicationVariants.all { variant ->
variant.mergeResources.doLast {
def dir = new File("${buildDir}/intermediates/res/merged/${variant.dirName}") //iterating through resources, prepared for including to APK (merged resources)
println("Resources dir " + dir)
dir.eachFileRecurse { file ->
if(file.name.endsWith(".xml")) { //processing only files, which names and with .xml
String content = file.getText('UTF-8')
if(content != null && content.contains("Bill")) {
println("Replacing name in " + file)
content = content.replace("Bill", "Will") //replacing all Bill words with Will word in files
file.write(content, 'UTF-8')
}
}
}
}
The answers here no longer work with latest android build tools, but here is a new version that is working for me.
android.applicationVariants.all { variant ->
variant.mergeResources.doFirst {
variant.sourceSets.each { sourceSet ->
sourceSet.res.srcDirs = sourceSet.res.srcDirs.collect { dir ->
def relDir = relativePath(dir)
copy {
from(dir)
include '**/*.xml'
filteringCharset = 'UTF-8'
filter {
line -> line
.replace('Bill', 'Will')
}
into("${buildDir}/tmp/${variant.dirName}/${relDir}")
}
copy {
from(dir)
exclude '**/*.xml'
into("${buildDir}/tmp/${variant.dirName}/${relDir}")
}
return "${buildDir}/tmp/${variant.dirName}/${relDir}"
}
}
}
}
create file gradle.properties in directory with build.gradle of your app (module, not project). In this app place string like
GOOGLE_MAPS_API_KEY = ishufhiaushdiasdh
add field 'buildConfigField' in your build.gradle file in buildTypes section. It will look like:
buildTypes {
debug {
buildConfigField "String", "GOOGLE_MAPS_API_KEY", String.format("\"%s\"", GOOGLE_MAPS_API_KEY)
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
buildConfigField "String", "GOOGLE_MAPS_API_KEY", String.format("\"%s\"", GOOGLE_MAPS_API_KEY)
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
After compiling project you'll find generated value as static String in BuildConfig class.
Why not just use the dynamic string formatting:
<string name="my_message">%1$s is on duty today</string>
in one build variant:
<string name="name">Bill</string>
in another build variant:
<string name="name">Will</string>
in your code:
String text = String.format(getResources().getString(R.string.my_message), getResources().getString(R.string.name));
One solution, though not applicable in all cases, is to use a Copy task with a filter directive :
task replace (type: Copy) {
from "sources" {
include "srcStrings.xml"
filter { line -> line.replaceAll("Will", "Bill") }
}
into "strings.xml"
}
You should observe the results obtained with replace() and replaceAll() when processing the lines. And take into account that the absolute directory of the file to be processed exists (not necessarily in res.srcDirs)
sourceSets {
flavor {
println res.srcDirs
task testA() {
def relativeDirFile = 'src/flavor/res'
def file = "${relativeDirFile}/values/strings.xml"
def absoluteDirFile = "${rootDir}/module/${file}"
def buildDirToCopy = "${buildDir}/tmp/${relativeDirFile}/values"
copy {
from(absoluteDirFile)
filter {
String line ->
println line
line.replace("(.*)", line) // To keep the line
// OR
line.replaceAll("(.*)", "") // To erase the line
}
into(buildDirToCopy)
}
res.srcDirs += (buildDirToCopy + "/strings.xml")
// Check the directories that were included in the flavor.
println res.srcDirs
}
}
}
Finally you could extract the task into another gradle file
I also recommend deepening the use of regex.
GL
I am having a requirement where i am having 4 flavor and each of this flavor there are group of two flavor have common source code while they are different only by one specific parameter defined in buildConfigField. Issuse i am facing is that while i am creating build for configuration let say "halfsmiledebug" i am not getting source code from halfsmile folder into main source code folder. I am getting compilation error.
My Project Structure is
SmileProject
|
|---SmileAndroidApplication
|--src
|-main
|-halfsmile
|-fullsmile
and there are 4 flavour
-halfsmilewithyellowteeth
-halfsmilewithwhiteteeth
-fullsmilewithyellowteeth
-fullsmilewithyellowteeth
i had defined source dir as
sourceSets{
main
{
java.srcDirs = ['src/main/java']
}
halfsmilewithyellowteeth
{
java.srcDirs = ['src/halfsmile/java']
}
halfsmilewithwhiteteeth
{
java.srcDirs = ['src/halfsmile/java']
}
fullsmilewithyellowteeth
{
java.srcDirs = ['src/halfsmile/java']
}
fullsmilewithwhiteteeth
{
java.srcDirs = ['src/halfsmile/java']
}
productFlavors
{
halfsmilewithyellowteeth
{
buildConfigField "String", "TYPEOFTEETH", "\"YELLOW\""
}
halfsmilewithwhiteteeth
{
buildConfigField "String", "TYPEOFTEETH", "\"WHITE\""
} fullsmilewithyellowteeth
{
buildConfigField "String", "TYPEOFTEETH", "\"YELLOW\""
}
fullsmilewithwhiteteeth
{
buildConfigField "String", "TYPEOFTEETH", "\"WHITE\""
}
}
}
Here is an example how you can easily implement it.
SpecificParameter.java
public class SpecificParameter {
public static ParameterType getParameter() {
return parameter;
}
}
and in your buildConfigField, you can set the parameter like,
parameter = SpecificParameter.getParameter();
and put 4 SpecificParameter.java to your flavor packages. This way your flavors will work like an adapter and your build file will be single and get your parameter from your flavors.
Edit:
IF you are using the buildconfig as I understand, you can use it like this, and enums can help you with these, better than a string but thats my opinion :).
TeethType.java
public enum TeethType {
YELLOW, WHITE
}
and in your gradle
productFlavors {
halfsmile {
...
}
fullsmile {
...
}
}
buildTypes {
whiteteeth {
...
buildConfigField "...TeethType", "TEETHTYPE", "...TeethType.WHITE"
}
yellowteeth {
...
buildConfigField "...TeethType", "TEETHTYPE", "...TeethType.YELLOW"
}
}
this way you will have 2 flavors and 2 build types. In your build variants, you will see 4 kinds of them. You can access BuildConfig.TEETHTYPE anytime you want to access your type.