Set constant value with gradle - android

I just moved to the android gradle build system and it is not clear for me how to set constant values.
e.g. I have
API.java which contains
BASE_URL = "http://debug.server.com"
but when gradle builds release I need BASE_URL to be "http://release.server.com"
How to replace this value at the build time?

Gradle generates a class called BuildConfig which contains static members (such as the boolean DEBUG, which is set to true for debug variants).
You could either query this in java like so:
if (BuildConfig.DEBUG) {
BASE_URL = "http://debug.server.com"
} else {
BASE_URL = "http://release.server.com"
}
or the same thing as a one-liner:
BASE_URL = BuildConfig.DEBUG ? "http://debug.server.com" : "http://release.server.com"
OR
You could actually set the BASE_URL inside the the BuildConfig class using gradle like so:
android {
buildTypes {
debug {
buildConfigField "String", "BASE_URL", '"http://debug.server.com"'
}
release {
buildConfigField "String", "BASE_URL", '"http://release.server.com"'
}
}
}
Note the single and double quotations around the value in gradle, as others have mentioned in the comments. This way, the double quotes become part of the value.
As a result, the static reference BuildConfig.BASE_URL would point to the corresponding URL (i.e. "debug.server.com" for debug, "release.server.com" for release)

Answer of fifer-sheep is correct. Just wanted to leave a solution for more than two environments.
Two flavors for staging and live.
productFlavors {
staging {
...
}
production {
...
}
}
Whole app config relies on the current ENV. Using:
public static String ENV_DEVELOPMENT = "development";
public static String ENV_STAGING = "staging";
public static String ENV_PRODUCTION = "production";
ENV = BuildConfig.DEBUG ? ENV_DEVELOPMENT : BuildConfig.FLAVOR;
I can switch between all different ENVs while testing locally but force staging/live settings if released.

Related

Allow overriding buildConfigField for local debugging, but prevent commit

My Android app communicates with a server defined in a buildConfigField in app/build.gradle:
buildConfigField "android.net.Uri", "server", 'android.net.Uri.parse("https://app.example.com")'
(We use a buildConfigField because this allows different "flavors" of the app to point to different servers.)
Sometimes developers need to change this value to point to a local copy of the server software running on a different server.
The problem I have is that these developers regularly forget they have made these changes and then blindly commit all of their changes to the git repository, thus breaking the app for everyone else until someone else changes it back to the original value.
Is there a way to allow developers to override the buildConfigField from a separate (.gitingored) file so they cannot commit their changes?
Inspired by a comment from #TTT, here is how I am now generating BuildConfig fields from a .env file (or Environment Variables). To prevent sharing unwanted variables (such as other system variables that might have sensitive data), it only handles variables with names that begin with ANDROID_.
This requires the co.uzzu.dotenv.gradle Gradle plugin to be set up in the project.
androidComponents {
onVariants(selector().all(), { variant ->
env.allVariables.each {
if (it.key.startsWith("ANDROID_")) {
def key = it.key.substring(8)
.split("_")
.collect {
it.substring(0,1).toUpperCase(Locale.ROOT) +
it.substring(1).toLowerCase(Locale.ROOT)
}
.join("")
key = key.substring(0,1).toLowerCase(Locale.ROOT)+key.substring(1)
def type = "String"
def wrapper = '"%s"'
def value = it.value
if (value.isEmpty()) {
wrapper = '%s'
value = null
} else if (value.matches("-?\\d+")) {
type = "int"
wrapper = '%s'
} else if (value.matches("-?\\d+(.\\d+)")) {
type = "double"
wrapper = '%s'
} else if (value.matches("https?://.*")) {
type = "android.net.Uri"
wrapper = 'android.net.Uri.parse("%s")'
}
variant.buildConfigFields.put(key, new BuildConfigField(type, String.format(wrapper, value), "Field from .env"))
}
}
})
}
It supports variables that are boolean, int, double, String, or android.net.Uri (though only String can be null since it cannot determine the type without a value).

How to call buildConfigField() from another function in Gradle?

Here's an extremely simplified version of what I'm trying to do in Gradle for my Android app:
android {
buildTypes {
debug {
buildConfigFieldMyWay("keyName", "keyValue")
buildConfigField("String", "keyName", "keyValue") }
}
}
}
def buildConfigFieldMyWay(String keyName, String keyValue) {
buildConfigField("String", keyName, keyValue)
}
The real version is more complex, which is why it would be nice to organize what I'm doing into separate functions.
The problem is that when I do a Gradle sync, I get this error:
No signature of method: build_byqgds5lao5ipgp4gk5ftyud4.android() is applicable for argument types: (build_byqgds5lao5ipgp4gk5ftyud4$_run_closure2) values: [build_byqgds5lao5ipgp4gk5ftyud4$_run_closure2#536e468e]
I think this has to do with the DSL Gradle uses with Android; that when buildConfigField() is called inside of android { buildTypes { debug, that it is running buildConfigField() on a BuildType object. If I call it from a separate function, there is no BuildType for it to operate on.
So I either need to modify the DSL so that my function (buildConfigFieldMyWay()) can be called in android { buildTypes { debug and operate on the BuildType object, or I need to pass the BuildType to the function. There's likely some simple syntax I need to use here that I'm not finding. What is it?
You can pass the build type inside the function buildConfigFieldMyWay:
def buildConfigFieldMyWay(buildType, String keyName, String keyValue) {
buildType.buildConfigField("String", keyName, keyValue)
}
And then, you can get the build type using it:
android {
buildTypes {
debug {
buildConfigFieldMyWay(it, "keyName", "keyValue")
buildConfigField("String", "keyName", "keyValue")
}
}
}

How to unit test the app behavior for different Gradle buildTypes in Android?

I am trying to test the behavior of different stages in an Android app. Considering the official docs, I use different Gradle BuildTypes in the module's build.gradle to define the settings needed for each stage.
Each buildType block contains a String with a similar key STAGE_NAME and a different value for each stage for example test_stage, and uat_stage.
The value of those is being accessed in the app using BuildConfig.STAGE_NAME to change some of the parameters like API endpoints accordingly.
But the problem is, there seems to be no official way to unit test the resulting behavior for each stage.
The Android docs, mention a way to change the test buildType which explicitly defines which buildType is being used for testing, but I was wondering if there is another approach to test the resulting behaviors.
The module's build.gradle file:
android {
buildTypes {
release {
...
}
debug {
applicationIdSuffix ".debug"
buildConfigField "String", "STAGE_NAME", "\"debugStage\""
...
}
testStage {
initWith(debug)
buildConfigField "String", "STAGE_NAME", "\"testStage\""
...
}
uatStage {
initWith(debug)
buildConfigField "String", "STAGE_NAME", "\"uatStage\""
...
}
}
}
Example Retrofit interface which is supposed to use different API URLs for each stage by accessing the STAGE_NAME and find out which of the URLs should be used as API_URL:
interface FakeService {
#GET("stuff")
suspend fun getStuff(): List<Stuff>?
...
companion object {
// Different urls which should be used for each stage:
private const val URL_DEBUG = "https://debug_api.example.com/"
private const val URL_TEST = "https://test_api.example.com/
private const val URL_UAT = "https://uat_api.example.com/"
private const val URL_PROD = "https://prod_api.example.com/"
// This is where we access the STAGE_NAME to figure out which stage url should be used
val API_URL_TO_USE = getUrl(BuildConfig.STAGE_NAME)
fun getUrl(stageName: String?): String {
return when (stageName) {
"debugStage" -> URL_DEBUG
"testStage" -> URL_TEST
"uatStage" -> URL_QA
else -> URL_PROD
}
}
}
}
For example in this retrofit interface, I would like to test the behavior of getUrl function to make sure it returns the right URL for each stage.
UPDATE:
After looking at this question, I was wondering maybe one way of testing this, could be mocking the BuildConfig. But since the STAGE_NAME is only available after a successful build, it can not be mocked normally.

How to pass customizable property when Android app launches

I would like to launch my Android app in such a way that I can set some external variable that my app can read. It would be nice if this was possible either in Gradle or as part of the debug/run configuration.
In essence, I would like to test for a variable to see if it is set. In this example I would like to set USE_FAKE_DATA:
if (USE_FAKE_DATA) {
...
} else {
...
}
One way is to use build variants and I have done this before. But I'm wondering if another way has been made available.
Gradle File
android {
buildTypes {
debug {
buildConfigField "boolean", "USE_FAKE_DATA", "true"
}
release {
buildConfigField "boolean", "USE_FAKE_DATA", "false"
}
}
}
Java File
class Test extends Activity {
#Override
public void onCreate(Bundle data) {
if (BuildConfig.USE_FAKE_DATA) {
...
} else {
...
}
}
}
Please refer this answer for more.

How to create flavor specific Android Lint baseline file

As per https://developer.android.com/studio/write/lint.html#snapshot we can create a Lint warning baseline file.
The problem is that I have multiple flavors, each having their own sourceSets. Some files are used in a single flavor.
When I generate the baseline file, it's always specific to a variant. Which means that it's invalid for the other variants, ie it will miss some existing issues.
I have tried putting the
lintOptions {
baseline file("lint-baseline.xml")
}
in the build and flavor blocks, but it won't generate multiple baselines.
Has anyone managed to generate flavor specific lint baseline file? And if so how?
Thanks!
I was trying the same thing and found a way of doing it.This create diff file for release and debug.You can put your custom logic in getfileName
lintOptions {
baseline file(getFileName())
checkAllWarnings true
warningsAsErrors true
abortOnError true
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().args.toString()
println tskReqStr
if (tskReqStr.contains("Debug")) {
return "debug"
} else {
return "release"
}
}
private String getFileName(String command) {
return getCurrentFlavor() + "-lint-baseline.xml"
}
I couldn't make the above answer exactly work as I got errors when trying to define the method in the build.gradle file.
Using himanshu saluja's answer this is what worked for me.
My root project's gradle file has:
ext.getLintFileName = {
Gradle gradle = getGradle()
String taskReq = gradle.getStartParameter().getTaskRequests().args.toString()
if(taskReq.contains("Debug")) {
return "debug-lint-baseline.xml"
} else {
return "release-lint-baseline.xml"
}
}
And the sub project's gradle file inside the android block uses the value like this:
lintOptions {
baseline file(rootProject.ext.getLintFileName)
checkDependencies true
abortOnError true
absolutePaths false
}
Given that the baseline feature is on LintOptions and this one is AFAIK not capable of being variant aware, this will not work out of the box.
You could file a feature request on https://b.android.com though.
according to my GitHub sample code:
1- add the following function to your app-level build.gradle file:
def getPath() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern
String path
String fileName = "lint-baseline"
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else if (tskReqStr.contains("generate"))
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
else if (tskReqStr.contains("lint"))
pattern = Pattern.compile("lint(\\w+)(Release|Debug)")
if(pattern != null) {
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find()) {
path = matcher.group(1).toLowerCase() + matcher.group(2).toLowerCase()
return "lint-baselines/${path}-${fileName}.xml"
} else {
return "lint-baselines/${fileName}.xml"
}
}
return "lint-baselines/${fileName}.xml"
}
this function creates a specific path for each build variants. you can customize the file name by changing the "fileName" variable.
2- add the following line to lintOption scop of your app-level build.gradle file:
lintOptions {
...
// Use (or create) a baseline file for issues that should not be reported
baseline file("${getPath()}")
...
}
3- run linter for each of build varients.
for example type the following command in the terminal tab of Android studio in the root of project:
gradlew app:lintMyAppRelease
app = your module name
MyAppRelease = your build varient
4- Done

Categories

Resources