AndroidPub
Published in

AndroidPub

Gradle Tasks with Kotlin

Recently, I had the need to send a Slack message from our CI server during our Android app build. Since Android is built with Gradle and in my opinion Gradle is just pure awesomeness, I thought why not just have a task that does just that.

The first thing I did was to look for a pre-made solution in the form of a Gradle plugin. I found one, but it wasn’t flexible enough as I wanted it to be. So I decided to create my own custom task to do that and while I’m at it have it written in Kotlin (of course :P).

Project structure

Creating a custom Gradle task can be done in different ways. You can just write Groovy code in your build.gradle or make it a bit nicer and have it in a separate Gradle file, say slack.gradle, and apply it to your app’s build.gradle with apply from: 'slack.gradle'. But that would be Groovy and not Kotlin. Although there’s a Gradle Kotlin DSL work-in-progress, but after checking out the examples, I decided not to try it since it looks like it adds more code to build.gradle than it reduces and overall the DSL doesn’t look as nice as it looks in Groovy (just my opinion). I’ll definitely have another look when the DSL matures.

Another option is to make a standalone Gradle plugin project. That might have been my first choice, but I planned on having only one class that defines the custom task and less code is always better.

The last option is to have a folder called buildSrc in your project’s root folder and put everything there. This option felt like the way to go in this case.

Setting up Kotlin support

Surprisingly, I couldn’t find anything about how to hook up Kotlin with Gradle in the buildSrc setup. Most of the tutorials I found were talking about creating a Gradle plugin project with Kotlin and then there was the Gradle Kotlin DSL project, etc. Hence I decided to write my own tutorial.

First, create a folder called buildSrc in your project’s root. This folder is automatically detected by Gradle so you don’t need to configure it anywhere. Now create a build.gradle file inside buildSrc. We will use it to set up the Gradle Kotlin plugin and library dependencies that our custom task will use. In this case, I’m going to depend on okHttp to do the networking.

buildscript {
ext.kotlin_version = '1.1.51'
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

apply plugin: 'kotlin'

ext {
okHttpVersion = "3.8.1"
}

repositories {
jcenter()
}

dependencies {
implementation gradleApi()
implementation "com.squareup.okhttp3:logging-interceptor:$okHttpVersion"
implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
}

compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
  • Note that we also have dependency on gradleApi() which is a built-in method in Gradle.

One you’re done with build.gradle, create folders src/main/kotlin inside buildSrc. Sync your project with Gradle in Android Studio (the little green icon) and let’s continue to the custom task class creation. After the sync, Android Studio has identified src/main/kotlin as a source folder so you can now create packages there. Create your package however you want, mine is going to be org.codepond.gradle.slack.

Writing the task class in Kotlin

Inside this package you created, create Kotlin class SlackTask.kt with the following code:

package org.codepond.gradle.slack

import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.logging.HttpLoggingInterceptor
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class SlackTask : DefaultTask() {
lateinit var messageText: String
lateinit var webhookUrl: String

@TaskAction
fun sendMessage() {
val logger = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { m -> logger.debug(m) })
logger.level = HttpLoggingInterceptor.Level.BODY
val okhttp = OkHttpClient.Builder().addInterceptor(logger).build()
val body = RequestBody.create(MediaType.parse("application/json"), "{\"text\":\"${messageText.replace("\"", "\\\"")}\"}")
val request = Request.Builder()
.url(webhookUrl)
.post(body)
.build()
okhttp.newCall(request).execute()
}
}

A few important things to notice here (marked in bold):

  • The class must be defined as open. Kotlin classes cannot be extended by default (similar to final class in Java) and if not set correctly, Gradle will try to proxy your class and fail the build.
  • We extend DefaultTask since well, it’s a Gradle task ;)
  • We annotate our entry method with @TaskAction so Gradle knows what to execute.
  • We declare the properties messageText and webhookUrl as lateinit var properties since these two properties are mandatory in the task spec and we don’t want any default values. Gradle will make sure that these properties are assigned when configuring the task. If we don’t set them, the build will fail saying that these properties were not set. If you want to have optional properties for your task, you can annotate them with @Optional annotation.
  • Now considering the code itself, we set up OkHttp with a logging interceptor that logs directly to Gradle debug level and then it’s just standard OkHttp POST request. You can use JSON marshalling library if you have a complex JSON, but for this example, it was easier to just write it by hand.
  • It’s important to notice that the request is executed synchronously with execute() instead of enqueue(). Otherwise if you do it async, the task will just complete with build successful without actually ever sending the request (or sending it, but not receiving the response since we’re not blocking the thread).

Using the custom task in build.gradle

Now to the last part. We’ve got the custom task class in place, now we need to use it in our build.gradle. To do that you have to include an import statement at the beginning of the build.gradle file.

This wasn’t so obvious since in the official Gradle docs, it says that all classes under buildSrc will be available directly in build.gradle, but at least according to my tests this was true only when the class was written in Groovy — and not in Kotlin.

In your build.gradle (doesn’t matter if it’s the root or the app/module specific one — wherever you’d like to define this task) add the following:

import org.codepond.gradle.slack.SlackTask

task helloSlack(type: SlackTask) {
messageText = "Hello there!"
webhookUrl = "your webhook URL here"
}

More info on Slack Incoming Webhooks and how to generate them here.

And now to run this task from command line type: ./gradlew helloSlack.
If you’re experiencing issues making the request, remember we set up OkHttp logging interceptor output to Gradle debug level logger. To show the logs run the same command with -d e.g.: ./gradlew helloSlack -d.

There you have it, you’ve successfully written a custom Gradle task in Kotlin that doesn’t require you to have a separate Gradle plugin project setup. Everything in one project under Android Studio!

If you liked this article please like and follow me here and on twitter.

https://twitter.com/nimroddayan

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store