Upcoming and OnDemand Webinars View full list

Writing your Gradle build scripts in Kotlin on Android

Andrew Marshall

The folks at Gradle recently released version 5.0, which means that us Android developers now have access to Gradle Kotlin DSL v1.0! This means that we can write Gradle build scripts in our favorite language, Kotlin.

What is it?

Gradle Kotlin DSL is a domain specific language built with the express purpose of defining Gradle build plans. This has all of your favorite functions and assignments from the Gradle Groovy DSL, but now in Kotlin.

The full feature set of Gradle Kotlin DSL is supported on less IDEs than Groovy, but Android Studio and IntelliJ IDEA support everything we need, so most Android developers shouldn’t have a lot of issues.

Why?

I don’t know about you, but editing Gradle Groovy files is not my favorite part about Android development. Groovy is dynamically typed, so Android Studio has a tough time providing you with intelligent hints about what methods you can call, what parameters they take, and when you’ve done something wrong. Furthermore, since Gradle scripts are the only thing I typically see Groovy in, the syntax is way more difficult for me to parse than that of Kotlin, which I use every day during app development.

By writing our Gradle scripts in Kotlin instead of Groovy, we can solve both these problems. Since Kotlin is statically typed, it’s simpler for the IDE to fill in helpful hints about the code. Plus, since we use Kotlin for day-to-day development on Android apps, editing those Gradle files isn’t as much of a cognitive load since we’re familiar with the language’s syntax.

How do I do it?

When you create a project in Android Studio, it creates a Gradle script using Groovy by default. To continue past this, we’ll convert the default Gradle scripts to use Kotlin instead. We’ll first make some minor syntactical changes to our Groovy scripts to inch them closer to their eventual Kotlin forms, so that when we actually change the file extension, it won’t have as many errors.

Use the right version of Gradle

Depending on the default version of Gradle for your version of Android Studio, your project may have a version of Gradle from before Gradle Kotlin DSL was fully supported. In your app’s gradle-wrapper.properties file, change the Gradle version to 5.0 so that you’ll have the stable 1.0 version of Gradle Kotlin:

distributionUrl=https://services.gradle.org/distributions/gradle-5.0-all.zip

Fix your single quotes

In most places, Groovy doesn’t care whether you use single quotes or double quotes to encapsulate strings. Kotlin is pickier: it requires double quotes. Do a find and replace (cmd+R macOS/ctrl+R Windows) for all the single quotes in your Gradle scripts and change them all to double quotes.

Use updated syntax to apply plugins

Any plugin applications using the legacy apply plugin syntax should be replaced with the newer plugins DSL. This new syntax allows Gradle to perform optimizations for loading your plugins, and helps the IDE with providing hints about the plugin classes.

// legacy
apply plugin: 'com.android.application'
// plugins DSL
plugins {
  id("com.android.application")
}

However, the plugins DSL does have limitations, so if you can’t use it in your case, you can still use the legacy apply plugin in Kotlin after converting the syntax.

Explicitly call functions or assign values

A feature of Groovy is the ability to assign a value or call a method with the exact same syntax:

// this is a method call
targetSdkVersion 28
// this is an assignment
versionCode 1

That’s not the case in Kotlin, and Groovy doesn’t require this syntax, so we can go ahead and change those to their more explicit counterparts right now:

// this is a method call
targetSdkVersion(28)
// this is an assignment
versionCode = 1

Note that Groovy uses a similar property access paradigm as Kotlin, using the underlying setProperty(5) function when you type property = 5. Therefore, the versionCode = 1 above could also be changed to setVersionCode(1) to the same effect, but because we also have property access in Kotlin, we can simply use the assignment operator.

If you’re not sure if a Groovy line should be converted to an assignment or a method call, you can always use quick info (ctrl+J macOS/ctrl+Q Windows) to pop up some information about the item where the cursor is. They all will show methods, but the ones that follow the JavaBean naming convention (get..., set...) can be converted to assignments in Kotlin.

Since most of the dependencies block in app/build.gradle is typically formatted in a similar way, I like to use a regex find and replace to reformat all of the implementation lines in one move:

// find regex
mplementations?(.*)$
// replace with regex
mplementation($1)

Change the file extensions

Now we’ve gotten the scripts as close as possible to Kotlin syntax while still being Groovy, so we’re ready to actually convert the files to Kotlin scripts instead of Groovy scripts. Rename the file names from build.gradle to build.gradle.kts to indicate that they’re now Kotlin script files.

Of course, this will cause a lot of red syntax highlighting, since we haven’t actually converted everything to Kotlin yet, but we’ll get there. Do a Gradle sync now, and at the end of every section below. If Android Studio ever tells you that “There are new script dependencies available” via a pop-down at the top of the window, choose the Enable auto-reload option to automatically apply them.

Fix any global variables

In Groovy, we had access to the ext object, which allows us to set variables that can be accessed from any of the inheriting Gradle scripts (this is often used to set version numbers for various dependencies). We can use extra.set("constraint_layout_version", "1.1.3") with rootProject.extra.get("constraint_layout_version") but this doesn’t give us the benefit of autocomplete for our properties, since they need to be accessed via those key strings.

We’ll instead use a buildSrc module to create an external store for these globally-needed variables. In the root directory of your project, create the following directory path: buildSrc/src/main/kotlin. Within the buildSrc directory, create a file called build.gradle.kts and use it to apply the Kotlin DSL plugin:

plugins {
  // note the backtick syntax (since `kotlin-dsl` is 
  // an extension property on the plugin's scope object)
  `kotlin-dsl` 
}

repositories {
    jcenter() // this is needed to download dependencies for kotlin-dsl
}

Within the buildSrc/src/main/kotlin directory, create a .kt file. It doesn’t matter what you name it, but since I usually use it for dependency declaration and versioning, I call it dependency.kt. You can then create objects with properties, and these will be able to be accessed from any of your build scripts.

object Versions {
    const val appCompat = "28.0.0"
    const val constraintLayout = "1.1.3"
}

object Deps {
    const val appCompat = "com.android.support:appcompat-v7:${Versions.appCompat}"
}

You can then use these in your app’s dependencies block, or anywhere else you need access to global variables for your scripts.

// can reference directly
implementation(Deps.appCompat)
// can also use Kotlin's string concatenation
implementation("com.android.support.constraint:constraint-layout:${Versions.constraintLayout}")

Change the buildTypes block in app/build.gradle.kts

In Groovy, our buildTypes block sets up our different build types like so:

buildTypes {
  release {
    ...
  }
  debug {
    ...
  }
}

It’s difficult to tell, but the general idea of what’s happening here is that it’s accessing a map of BuildTypes, which are mapped to String keys. All Android projects have both release and debug build types by default, but you can also define custom-named build types here as well. Unfortunately, the Kotlin equivalent for this is a bit hidden: inside the buildTypes block, the scope is a NamedDomainObjectContainer<BuildType>, which (along with its parent classes) has several functions for accessing these string keys:

// from parent class NamedDomainObjectCollection
fun getByName(String name, Action<BuildType> configureAction): BuildType
// from NamedDomainObjectContainer
fun create(String name, Action<BuildType> configureAction): BuildType
fun maybeCreate(String name): BuildType

We can use getByName if we already know the container holds a BuildType with a given name – this is the case for release and debug. If we want to make a custom-named build type, we can use the create method. However, the downside of both of these is that they can throw exceptions. maybeCreate protects us from this – it will first look for a pre-existing build type with the name given as an argument, but if that doesn’t exist, it will create it. However, maybeCreate doesn’t have an Action parameter like the other two, so we’ll have to use our trusty apply after we create the build type:

buildTypes {
  maybeCreate("release").apply {
    isMinifyEnabled = false
    ...
  }
}

Note I also change the minifyEnabled = false to be isMinifyEnabled instead, since that’s the actual name of the variable in BuildType.

Change Groovy’s map and list syntax to Kotlin’s

In this Groovy line:

// before
implementation(fileTree(include: ["*.jar"], dir: "libs"))

we’re asking Gradle to include any jar files from the libs directory by supplying them as a FileTree. To build this FileTree, we give the method a map of named properties, one of whose value is a single-element list. We’ll need to change this map and list to their Kotlin equivalents:

// after
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))

Rewrite any tasks

At this point, Android Studio should be able to mostly parse your gradle scripts. However, any tasks that are still written in Groovy syntax will still be highlighted as incorrect. The following task is added in a new AS project by default:

task clean(type: Delete) {
  delete rootProject.buildDir
}

Here, we’re registering a task called “clean” of type Delete, which invokes the function Delete.delete(rootProject.buildDir) when run. In Kotlin, we can write it like this instead:

tasks {
  val clean by registering(Delete::class) {
    delete(rootProject.buildDir)
  }
}

All done!

We’ve completed the conversion of a set of Groovy Gradle scripts to Kotlin Gradle in an Android project!

As the Gradle Kotlin DSL version 1.0 was just released, there are still some unsupported features, and some minor bugs with Android Studio IDE support. However, the API is stable, and the converted script is able to build an Android project, along with improved support for code completion and editing hints from the IDE.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project