I'm dividing my app into multiple flavors, some of them should have HMS and some of them should not, so far I've managed to sucessfully divide source code but I can't get this part of gradle config to work as I expect it:
if(rootProject.ext.useHuaweiLib == true) {
implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.hms:push:5.0.1.300'
}
Here I'm adding libraries only if useHuaweiLib is true, here is flavor division in app module:
flavorDimensions "purpose"
productFlavors {
client {
dimension "purpose"
buildConfigField "boolean", "USE_HUAWEI_LIB", "false"
}
defaultConfig {
dimension "purpose"
buildConfigField "boolean", "USE_HUAWEI_LIB", "false"
}
huawei_client{
dimension "purpose"
buildConfigField "boolean", "USE_HUAWEI_LIB", "true"
rootProject.ext.useHuaweiLib = true
}
sdk {
dimension "purpose"
buildConfigField "boolean", "USE_HUAWEI_LIB", "false"
}
}
and variable declaration in root module:
ext {
buildToolsVersion = "32.0.0"
compileSdkVersion = 31
minSdkVersion = 23
targetSdkVersion = 30
versionCode = 1
releaseName = "1.0"
versionName = "1.0.0"
useHuaweiLib = false
}
As you can see the variable is false by default and is only set to true in huawei_client flavor, but for some reason even if I choose 'client' flavor I still get access
to HMS api. I want to be able to select a flavors with different set of sources and libs.
You can add the flavor as a prefix to all dependency imports. Ditch the if statement and just use this:
huawei_clientImplementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
huawei_clientImplementation 'com.huawei.hms:push:5.0.1.300'
This way, the two librarys will only be added to the huawei_client flavor.
Related
I am trying to add a second flavor dimension to an existing Android project. I already had a dimension for different environments (DEV, BETA, PROD), each with their own backend API and their own application id (to be able to install apps connected to several environments on the same device). And now I want to add another dimension for 2 variants of my app, a general one with all the features, and a specific one with a subset of features. And in addition to that, I still have the default debug and release build types I want to keep.
So here is what my configuration in build.gradle looks like so far:
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.example"
minSdkVersion 21
targetSdkVersion 30
versionCode 56
versionName "1.1.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [
appAuthRedirectScheme: 'com.example'
]
}
signingConfigs {...}
buildTypes {
debug {...}
release {...}
}
flavorDimensions "env", "variant"
productFlavors {
dev {
dimension "env"
applicationIdSuffix ".dev"
resValue 'string', 'backend_url', 'https://dev.example.com/api/v1/'
manifestPlaceholders = [
appAuthRedirectScheme: 'com.example.dev'
]
}
beta {
dimension "env"
applicationIdSuffix ".beta"
resValue 'string', 'backend_url', 'https://beta.example.com/api/v1/'
manifestPlaceholders = [
appAuthRedirectScheme: 'com.example.beta'
]
}
prod {
dimension "env"
resValue 'string', 'backend_url', 'https://example.com/api/v1/'
manifestPlaceholders = [
appAuthRedirectScheme: 'com.example'
]
}
general {
dimension "variant"
applicationId "com.example"
}
specific {
dimension "variant"
applicationId "com.example.specific"
}
}
...
}
As you can see, I will be able to have an application variant specific to each environment/variant flavor by combining applicationId and applicationIdSuffix.
But I also need to have the corresponding mapping in manifestPlaceholders, knowing that the appAuthRedirectScheme placeholder is not integrated inside of my project's manifest, but in the openId Appauth dependency, so I can't just have several manifests in the various flavor directories like I have read elsewhere.
Is there a way to define a build setting in build.gradle that is specific to each flavor dimension combination? In other words, I would like to have different values of manifestPlaceholders for devGeneral, devSpecific, betaGeneral, betaSpecific, prodGeneral and prodSpecific.
I don't specially advice this solution, but you could try the following approach:
applicationVariants.all { variant ->
if(variant.productFlavors.get(0).name == "dev") {
if (variant.buildType.name == "debug" ) {
variant.getMergedFlavor().manifestPlaceholders = [
appAuthRedirectScheme: 'com.example.dev'
]
}
if (variant.buildType.name == "release" ) {
variant.getMergedFlavor().manifestPlaceholders = [
appAuthRedirectScheme: 'com.example.dev.foo'
]
}
}
if(variant.productFlavors.get(0).name == "beta") { ... }
if(variant.productFlavors.get(0).name == "prod") { ... }
}
This is a question about package names in android. I currently have two build flavors in gradle. Production and Staging.
I have created a google play store account and i want users to alpha and beta test my app. The staging app currenly has a package name of:
com.mobile.myapp.staging while the production flavor has a package name of com.mobile.myapp.
so we have
com.mobile.myapp.staging vs com.mobile.myapp
in the end i clearly want to promote com.mobile.myapp to production not the staging. but i'd like the users to test with the staging variant for a long while (as its connected to staging apis . etc etc.)
How can i do this ? would i have to create two different apps in the google play store ? I am wondering if i have to do this as
they both have different package names. They both will be signed with the same keystore. Please help.
my gradle file looks like this:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
minSdkVersion project.ext.minimumSdkVersion
//check top level build.gradle file for attributes -
targetSdkVersion 25
applicationId "com.mobile.myapp"
versionCode 150010203
versionName 1.2.3
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//renderscriptTargetApi 25
//renderscriptSupportModeEnabled true
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dexOptions {
javaMaxHeapSize "6g"
}//for out of memory gc overhead error
lintOptions {
abortOnError false
}
productFlavors {
def STRING = "String"
def BOOLEAN = "boolean"
def TRUE = "true"
def FALSE = "false"
def FLAVOR = "FLAVOR"
def RETROFIT_LOG_ALL = "RETROFIT_LOG_ALL"
def BASE_ENDPOINT = "BASE_ENDPOINT"
staging {
// applicationId "com.mobile.myapp.staging"
buildConfigField STRING, BASE_ENDPOINT, '"https://api.flyingSaucerxx-staging.com"'
buildConfigField BOOLEAN, RETROFIT_LOG_ALL, TRUE
manifestPlaceholders = [appDrawerName: "FlyingSaucer-Staging"]
applicationIdSuffix '.staging'
versionNameSuffix '-STAGING'
}
prod {
buildConfigField STRING, BASE_ENDPOINT, '"https://api.flyingSaucerxx.com"'
buildConfigField BOOLEAN, RETROFIT_LOG_ALL, FALSE
manifestPlaceholders = [appDrawerName: "FlyingSaucer"]
}
}
}
///.. dependencies below
It is not possible to use different package names in Google Play Store for the same app.
So the only option you have is to change package name of your staging app to production one. And submit it to alpha/beta testers. And sure watch out to not promote it to production.
Another option is to use other delivery channels like hockeyapp or crashlitics beta.
Upgraded to Studio Canary build. My previous project of Telegram Messenger is giving following error.
Error:All flavors must now belong to a named flavor dimension. The flavor 'armv7' is not assigned to a flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html
What should I do? I have already seen that link but couldn't understand what to do. I have 3 build variants now, release,debug and foss.
If you don't really need the mechanism, just specify a random flavor dimension in your build.gradle or build.gradle.kts:
android {
...
flavorDimensions("default")
...
}
For more information, check the migration guide
After trying and reading carefully, I solved it myself.
Solution is to add the following line in build.gradle.
flavorDimensions "versionCode"
android {
compileSdkVersion 24
.....
flavorDimensions "versionCode"
}
Here you can resolve this issue, you need to add flavorDimension with productFlavors's name and need to define dimension as well, see below example and for more information see here https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html
flavorDimensions 'yourAppName' //here defined dimensions
productFlavors {
production {
dimension 'yourAppName' //you just need to add this line
//here you no need to write applicationIdSuffix because by default it will point to your app package which is also available inside manifest.xml file.
}
staging {
dimension 'yourAppName' //added here also
applicationIdSuffix ".staging"//(.staging) will be added after your default package name.
//or you can also use applicationId="your_package_name.staging" instead of applicationIdSuffix but remember if you are using applicationId then You have to mention full package name.
//versionNameSuffix "-staging"
}
develop {
dimension 'yourAppName' //add here too
applicationIdSuffix ".develop"
//versionNameSuffix "-develop"
}
If you want not to use dimensions you should use this line
android {
compileSdkVersion 24
...
flavorDimensions "default"
...
}
but if you want ti use dimensions you should declare your dimension name first and then use this name after
THIS example is from the documentations:
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"
...
}
}
}
...
I have used flavorDimensions for my application in build.gradle (Module: app)
flavorDimensions "tier"
productFlavors {
production {
flavorDimensions "tier"
//manifestPlaceholders = [appName: APP_NAME]
//signingConfig signingConfigs.config
}
staging {
flavorDimensions "tier"
//manifestPlaceholders = [appName: APP_NAME_STAGING]
//applicationIdSuffix ".staging"
//versionNameSuffix "-staging"
//signingConfig signingConfigs.config
}
}
Check this link for more info
// Specifies two flavor dimensions.
flavorDimensions "tier", "minApi"
productFlavors {
free {
// Assigns this product flavor to the "tier" flavor dimension. Specifying
// this property is optional if you are using only one dimension.
dimension "tier"
...
}
paid {
dimension "tier"
...
}
minApi23 {
dimension "minApi"
...
}
minApi18 {
dimension "minApi"
...
}
}
in KotlinDSL you can use like this :
flavorDimensions ("PlaceApp")
productFlavors {
create("tapsi") {
setDimension("PlaceApp")
buildConfigField("String", "API_BASE_URL", "https://xxx/x/x/")
}
}
If you have simple flavors (free/pro, demo/full etc.) then add to build.gradle file:
android {
...
flavorDimensions "version"
productFlavors {
free{
dimension "version"
...
}
pro{
dimension "version"
...
}
}
By dimensions you can create "flavors in flavors". Read more.
I need your help. I wish to add the build name of my app to the gradle file. The thing is I do NOT want it to be visible to user on the market, so this:
versionName "3.1.0.5486"
does not suits me. But I need it in a way so I can read it via code (In the about screen for example) and increment it each build. How can you advise me?
Just use standard versionCode parameter. You can read it from code using
BuildConfig.VERSION_CODE.
You can set the version code and name inside the defaultConfig element in the build.gradle:
defaultConfig {
versionCode 1
versionName "1.0"
}
And use it as is:
String.format(getString(R.string.some_text_with_int_placeholder), BuildConfig.VERSION_CODE);
If you want create your own variable just use buildConfigField, something like:
buildTypes {
debug {
buildConfigField "int", "FOO", "42"
buildConfigField "String", "FOO_STRING", "\"foo\""
buildConfigField "boolean", "LOG", "true"
}
}
And access it via BuildConfig.FOO
I used to have the following project flavors:
Apple
Orange
Originally the only difference was the applicationId/packageName. Now there is a single java file that is different. A custom ArrayAdapter to be exact. The solution was to create src/Apple and src/Orange and both inherit from src/main. I removed the java file from src/main and put a copy into src/Apple and src/Orange and modified it appropriately. All was good in the world.
Fast forward a few weeks, now there are about 10 java files that differ between Apple and Orange. Again... no big deal. Easy to handle. Separate java files in src/Apple and src/Orange.
Fast forward to today. I need to modify things up a bit, because I want to have a free and premium version of each. The free and premium versions only differ by a URL. I was going to simply create the new types called:
AppleFree
ApplePremium
OrangeFree
OrangePremium
I have a dilema though. Since now src/Apple and src/Orange have 10 different files that have been changed... if I change any java file in AppleFree I have to make sure I do the same in ApplePremium. I'm kind of at a crossroads and hope my question makes sense at this point. I have come up with three possible solutions, but I'm not sure how I would implement them/what would be the correct approach/the solution is not what I want.
Solution 1:
Use an if statement
if (BuildConfig.FLAVOR==appleFree) {//use free Url} else {// use premium url}
Issue: Both Urls are technically compiled into the apk. I do not want this.
Solution 2:
Have src/AppleFree and src/ApplePremium inherit from an src/Apple parent directory somehow.
Issue: Not sure how I would do this.
Solution 3:
Add the free and premium url right in build.gradle like so?
productFlavors {
appleFree {
applicationId "com.example.apple.free"
versionName "1.0"
url "http://freeurl.com"
versionCode 1
}
applePremium {
applicationId "com.example.apple.premium"
versionName "1.0"
url "http://premiumurl.com"
versionCode 1
}
orangeFree {
applicationId "com.example.orange.free"
versionName "1.0"
versionCode 1
url "http://freeurl.com"
}
orangePremium {
applicationId "com.example.orange.premium"
url "http://premiumurl.com"
versionName "1.0"
versionCode 1
}
}
Issue: Not sure how to make that work.
Any tips are helpful.
EDIT:
Final Solution?
flavorGroups 'fruit', 'paid'
productFlavors {
apple {
flavorGroup 'fruit'
}
orange {
flavorGroup 'fruit'
}
free {
flavorGroup 'paid'
}
premium {
flavorGroup 'paid'
}
appleFree {
applicationId "com.example.apple.free"
versionName "1.0"
buildConfigField 'String', 'BASE_URL', 'http://freeurl.com'
versionCode 1
}
applePremium {
applicationId "com.example.apple.premium"
versionName "1.0"
buildConfigField 'String', 'BASE_URL', 'http://premiumurl.com'
versionCode 1
}
orangeFree {
applicationId "com.example.orange.free"
versionName "1.0"
versionCode 1
buildConfigField 'String', 'BASE_URL', 'http://freeurl.com'
}
orangePremium {
applicationId "com.example.orange.premium"
buildConfigField 'String', 'BASE_URL', 'http://premiumurl.com'
versionName "1.0"
versionCode 1
}
}
There are many possible solutions to your problem. The most native-Gradle solution would be to use Flavor Dimensions, as documented in http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Multi-flavor-variants
This is also similar to what you were thinking about with Solution 2.
It would work something like this:
flavorDimensions 'fruit', 'paid'
productFlavors {
apple {
dimension 'fruit'
}
orange {
dimension 'fruit'
}
free {
dimension 'paid'
}
premium {
dimension 'paid'
}
}
This will give you build variants (and source folders) where it does the combination of all possibilities out of each flavor dimension, maintaining the same order as how the groups are specified in your flavorDimensions statement (i.e. it's appleFree, not freeApple), thus:
* appleFree
* applePremium
* orangeFree
* orangePremium
in your src/ folder, you can have these possibilities:
* src/main
* src/apple
* src/orange
* src/free
* src/premium
* src/appleFree
* src/applePremium
* src/orangeFree
* src/orangePremium
Solution 3
You can use the buildConfigField to specify constants that go in the BuildConfig class on a flavor-by-flavor basis:
productFlavors {
appleFree {
buildConfigField 'String', 'MY_URL', 'value1'
}
applePremium {
buildConfigField 'String', 'MY_URL', 'value2'
}
orangeFree {
buildConfigField 'String', 'MY_URL', 'value3'
}
orangePremium {
buildConfigField 'String', 'MY_URL', 'value4'
}
Solution 1
I was trying to work up something along the lines of Solution 1, but it won't work well for your exact use case. If you have an if condition in Java that tests against a boolean that's declared static final then the compiler can determine statically whether the code is reachable or not, and it will strip it if it's not. Thus:
static final boolean DEBUG = false;
...
if (DEBUG) {
// do something
}
The code in // do something won't get compiled at all. This is an intentional and documented behavior on the part of the Java compiler, and allows you to write expensive debug code that won't get compiled into your release binary. BuildConfig.DEBUG is declared as static final for this exact reason.
There's a BuildConfig.FLAVOR, but it's defined as String, and you don't get the same benefit:
static final String FLAVOR = "orange";
...
if (FLAVOR.equals("apple")) {
// do something
}
The compiler isn't smart enough to do static analysis, see that // do something is unreachable, and not compile it. Note that it will work fine at runtime, but that dead code will be included in your binary.
If it suits you, though, you could steal the buildConfigField approach from above and define an extra boolean variable in some variants that could allow code to be conditionally compiled in. This is more complex than defining the string directly as in Solution 3, but if you find yourself wanting to differentiate behavior without going through the trouble of making flavor-specific subclasses, you could go this route.
Here's how I implemented product flavors inheritance.
stageLt (extends baseLt) - app for Lithuania using stage API
productionLt (extends baseLt) - app for Lithuania using production API
stagePl (extends basePl) - app for Poland using stage API
stagePl (extends basePl) - app for Poland using production API
android {
flavorDimensions "default"
productFlavors {
def API_URL = "API_URL"
def PHONE_NUMBER_PREFIX = "PHONE_NUMBER_PREFIX"
def IBAN_HINT = "IBAN_HINT"
baseLt {
buildConfigField "String", PHONE_NUMBER_PREFIX, '"+370"'
resValue "string", IBAN_HINT, "LT00 0000 0000 0000 0000"
}
basePl {
buildConfigField "String", PHONE_NUMBER_PREFIX, '"+48"'
resValue "string", IBAN_HINT, "PL00 0000 0000 0000 0000 0000 0000"
}
stageLt {
dimension "default"
applicationId "lt.app.stage"
buildConfigField "String", API_URL, '"https://www.limetorrents.pro/"'
}
productionLt {
dimension "default"
applicationId "lt.app"
buildConfigField "String", API_URL, '"https://yts.mx/"'
}
stagePl {
dimension "default"
applicationId "pl.app.stage"
buildConfigField "String", API_URL, '"https://1337x.to/"'
}
productionPl {
dimension "default"
applicationId "pl.app"
buildConfigField "String", API_URL, '"http://programming-motherfucker.com/"'
}
}
// base product flavors will not show up in 'Build Variants' view.
variantFilter { variant ->
if (variant.name.startsWith("base")) {
setIgnore(true)
}
}
}
void setupProductFlavor(baseFlavor, flavor) {
flavor.buildConfigFields.putAll(baseFlavor.buildConfigFields)
flavor.resValues.putAll(baseFlavor.resValues)
flavor.manifestPlaceholders.putAll(baseFlavor.manifestPlaceholders)
// Note that other product flavor properties ('proguardFiles', 'signingConfig', etc.) are not merged.
// E.g. if in base product flavor you declare 'signingConfig', it will not be copied to child product flavor.
// Implement if needed.
}
// Merge 'parent product flavors' with 'child product flavors'
setupProductFlavor(android.productFlavors.baseLt, android.productFlavors.stageLt)
setupProductFlavor(android.productFlavors.baseLt, android.productFlavors.productionLt)
setupProductFlavor(android.productFlavors.basePl, android.productFlavors.stagePl)
setupProductFlavor(android.productFlavors.basePl, android.productFlavors.productionPl)