A bit of automation with Gradle Tasks
Gradle tasks make it real easy to automate the repetitive things we do, on a daily basis, aside from just building our APKs.
Signing a release APK
One of the first things that you can do is to automate the signing process of your app’s release build. Here are a few simple steps to do that
Create a file signing.gradle in /app directory (the file name can be anything but make sure it’s not tracked, so that you don’t accidently push your actual keystore credentials to your repo)
Paste the following into the file
ext.signing = [
storeFilePath : '/path/to/your/keystore-file',
storePassword : 'your-store-password',
keyAlias : 'your-key-alias',
keyPassword : 'your-key-password',
]
Extra properties — The ext
properties are like project scoped variables, and we can access them anywhere in our gradle files, in this way: project.signing.storeFilePath
.
In your build.gradle file, add the following
def signingFilePath = 'signing.gradle'
def performSigning = file(signingFilePath).exists()
if (performSigning) {
apply from: signingFilePath
}
and specify
android.signingConfigs
android {
if (performSigning) {
signingConfigs {
release {
storeFile file(project.signing.storeFilePath)
storePassword project.signing.storePassword
keyAlias project.signing.keyAlias
keyPassword project.signing.keyPassword
v1SigningEnabled true
v2SigningEnabled true
}
}
}
}
also specify
release
build type withandroid.buildTypes
buildTypes {
release {
... if (performSigning) {
signingConfig signingConfigs.release
}
}
}
Now if you run ./gradlew assembleRelease
it’ll generate a signed apk for you! 🙌
Gradle Tasks
In gradle everything is pretty much either a project or a task. Each build can have one or more projects — and projects are composed up of tasks. If you checkout your project’s gradle properties you’ll see all the available tasks listed there!
A project has a one-to-one relation with the build.gradle
file and whenever gradle initializes a project, it checks the settings.gradle
file to see which modules are included. It then creates a hierarchy of projects. Evaluation of build.gradle
is done for parent projects first and then the child projects. The dependencies are defined at project level, as well as on the task level.
We can write our custom tasks to do all sort of things.
In our example, we’d write some tasks to generate a release apk, move and rename that apk file to some other directory — after that send a notification to slack. The sequence would go something like this.
Verify target or destination folder exists where we want to move the apk
Build release apk
Verify it got generated succesfully
Copy/Paste it into the target folder
Rename the apk (to whatever we want)
Send a slack notification! (because why not?)
Task 1: Verify that the folder path exists
First thing we’d do is verify that the folder exists where we want to copy our apk to. The reason for doing this first is that we get an earlier error exception if something is not right (and not after the whole build process is completed, which takes a bit of time).
So… Let’s define our task…
def targetPath = file("/Your/Target/Path")
task verifyTargetPath {
doLast {
if (!targetPath.exists()) {
throw new GradleException("Target path not valid!")
}
}
}
To run this either use the terminal ./gradlew verifyTargetPath
, or run it from the Gradle projects tool windows.
The doLast
closure specifies the order of execution. We can also use doFirst
. If we don’t write the code inside these blocks, it will execute every time no matter what task runs. And we most certainly don’t want that!
Task 2: Build a release apk
Now to build a release apk, we want to run verifyTargetPath
first, then run the existing task assembleRelease
and finally do our thing where we copy the apk to the target folder. So our task will look something like
task buildReleaseApk(dependsOn:
['verifyTargetPath', 'assembleRelease']) {
doLast { // create an apk name using versionCode // make sure that the file doesn't already exist // verify that the build was generated and move it target
}
}
The keyword dependsOn
specifies the order of execution — first it’ll run verifyTargetPath
, then assembleRelease
and finally buildReleaseApk
.
Let’s fill in the code…
task buildReleaseApk(dependsOn:
['verifyTargetPath', 'assembleRelease']) {
doLast {
ext.versionCode = project.android.defaultConfig.versionCode
ext.apkName = "android-release-build${ext.versionCode}.apk"
// make sure that the file doesn't already exist
if (targetPath.list().contains(ext.apkName)) {
throw new GradleException("Build with versionCode ${ext.versionCode} already exists!")
} // verify that the build was generated successfully
ext.apk = file("build/outputs/apk/release/app-release.apk")
if (ext.apk.exists()) {
copy {
from ext.apk.absolutePath
into targetPath
rename { ext.apkName }
}
}
}
}
This task first verifies the target path, then generates a release build, and copies it to the target path. Beautiful!
Task 3: A notification to Slack
We can send messages to slack using webhooks. You can get a webhook url for your slack workspace, which let’s you post to different channels. All you need is a url and some payload data that you just have to post.
I’d be using curl to send the payload to webhook endpoint. So the task for that will look something like…
task buildAndSendNotification(dependsOn: 'buildReleaseApk') {
doLast {
def text = "Build `${buildReleaseApk.apkName}` moved to `${targetPath}`." def webHook = 'your-webhook-url' def channel = 'your-channel' def response = ['curl',
'-X',
'POST',
'--data-urlencode',
"payload={\"channel\": \"${channel}\", \"username\": \"Bot\", \"text\": \"${text}\", \"icon_emoji\": \":ghost:\"}", '${webHook}'].execute().text println response
}
}
Executing this task will post a message to the specified channel. For example you can have your apk moved to some cloud service directory, and with this you can notify others that the apk is being uploaded.
Similar to the tasks above, Gradle tasks can be utilized to make our lives a bit more easier, depending on what your workflow is!
That’s it for now! Hope the article was helpful!
Some resources: Difference between doFirst and doLast, Project and tasks, Gradle User Manual
Happy coding!