Gradle is a pretty good tool, especially now that its pretty much mandatory. But it seems like there's a lot of hidden features/lack of documentation, especially when it comes to things like white labelling an app or executing external scripts, even the syntax can be quite confusing if you've never used Groovy before.
Here is a list of really cool tips and tricks I've picked up over the last few years of using Android Studio and Gradle.
It's really bad to have hard coded passwords/keystore paths in your code, especially if you're working on an open source piece of code, so one thing I do to get around this is to use environment variables and Console input.
Simply define a console variable at the top of your build gradle
def console = System.console();
and now you can call console.readLine("Enter your password: ").trim()
to read input via the command line.
The downside to this is that console can be null, especially if you're launching from Android Studio, so make sure you do your null checks.
If you dont want to type in your password each time, you can always use environment variables (stored in your bash_profile file)
Simple call System.getenv("PASSWORD")
to access system environment variables. Make sure to source ~/.bash_profile
after editing it so your variables are properly refreshed into system
There are a lot of projects I work on that are white label (meaning the same code base but built with different build variables for different clients), I used to manage this by having a separate flavour of the app for each client, but as client base grew, this became more and more unmanageable, so instead I use flags when calling ./gradlew assembleRelease
to define variables for the build.
You can define a closure at the top of your build gradle like so
def getAppId = { ->
def id = project.hasProperty('appId') ? appId.toString() : null
if (id == null)
{
// default app id
id = "1"
}
println "App id is set to $id"
return id
}
This will look for the -P
flag "appId" when running the assemble command. You can access this simply by calling getAppId()
anywhere in your build gradle (example ./gradlew assembleRelease -PappId=1
.
You may want to dynamically set variables for a particular flavour of your app (for example a boolean for if the app is free or paid) and it's quite simple.
In your flavour block add the line
buildConfigField "String", "APP_ID", "\"123abc\""
As you can see the first parameter is the type, the second is the variable name, and the third is the value. Make sure you wrap Strings in escaped quotes else it will put the text in as-is and will generate a compile error.
You can now access this variable by calling BuildConfig.APP_ID
from anywhere in your code.
Similar to build config, you can define variables to be generated for res values in the same way, very useful if you need to dynamically change variables that are only loaded from XML resources such as Google Analytics.
In your flavour block (or defaultConfig) add the line
resValue "string", "ga_trackingId", "test"
As you can see the first parameter is the type, the second is the variable name, and the third is the value.
Now this is a cool little piece of functionality that is well hidden in the documentation. This basically allows you to inject variables into your manifest, and is actually used behind the scenes for applicationId
.
This is particularly useful for resources that are only loaded via the AndroidManifest.xml file such as google maps API key, or app name
in your flavour block (or default config) add the line
manifestPlaceholders = [
MAPS_TOKEN : "not_a_maps_token",
APP_NAME : "my app",
]
manifestPlaceholders
is a reserved array name that accepts a key value pairing (separated by colon). You can access these variables in your manifest by calling ${MAPS_TOKEN}
for example
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="${MAPS_TOKEN}"
/>
Sometimes you only want to do the above when you're actually making a release build and not during debug (adding a call to load signing key file will be called every time you run the app), this can be incredibly annoying and prevent you from actually running debug code, one way I've found to circumvent this is to add a check to ensure that release
isn't being called
def isRelease()
{
for (String taskName : project.getGradle().startParameter.taskNames)
{
if (taskName.toLowerCase().contains("release"))
{
return true
}
}
return false
}
This is a simple method that loops through the tasks being called to check if the keyword release
is present, if not then its safe to assume that its a debug development build being run from the IDE.
Here's an example of it being used
signingConfigs {
main {
if (isRelease())
{
storeFile getKeystoreFile()
storePassword getKeystorePassword()
keyAlias = getKeyAlias()
keyPassword = getKeyPassword()
}
}
}
Without the isRelease
call, the store methods will be called which in turn will prompt for input, even though I'm not making a release build. Note: you shouldn't need to use this unless you have the same multi-flavour setup I used to have.