Dependencies versions in Gradle Kotlin DSL

Kamil Seweryn
Published in
4 min readApr 24, 2020

Migrating Gradle scripts from Groovy to Kotlin can be exciting and satisfying. Especially for those projects where Kotlin is the source code language of choice. Probably like most Android developers, I learn Groovy syntax only for sake of making changes to build process, necessary minimum. Now when I can play with Kotlin syntax I feel I understand more from Gradle.

excited Stewie

One of commons tasks, when configuring Gradle build, is defining dependencies for our project. With multi-module structure often we repeat dependencies in multiple modules:

// module-a/build.gradle.kts
dependencies {
implementation("androidx.appcompat:appcompat:1.1.0")
}
// module-b/build.gradle.kts
dependencies {
implementation("androidx.appcompat:appcompat:1.1.0")
}

Issue we are dealing here with is defining library version twice. It is not a good practice since it may lead to mismatch in future updates (when forgetting updating one place) and in consequence unpredicted behaviour on runtime. We need single source of truth and use the same versions throughout the project. Lets look at different ways you can solve this issue…

buildSrc directory

buildSrc is a directory in your project structure which Gradle automatically compiles and puts it in the classpath of your build scripts. This makes it a good candidate for defining common logic and allows for declaration to be available over all project modules. You don’t even have to put any import or apply from: in order to have the visibility on the defined values. You can read more in the documentation here.

In short, what is needed:

Step 1: Define values in any Kotlin file under buildSrc/src/main/kotlin

const val appCompatVersion = "1.1.0"

Step 2: Refer to it in any build.gradle.kts file

dependencies {
implementation("androidx.appcompat:appcompat:$appCompatVersion")
}

When you set this up it works like a charm for this purpose. From my research, when project uses Gradle Kotlin DSL, this is the most popular way for defining dependencies versions (often combined with dependencies names next to it). Additionally, you can place custom tasks or binary plugins there if you have such.

You can look closer on all the required changes in this sample commit.

Side note: versions defined this way (and other ways presented below) won’t be visible for buildSrc/build.gradle.kts script, so we can’t have every dependency versions in one common place.

Also, creating directory structure in a separate buildSrc module only to achieve this simple task, feels like an overkill. Lets take a look at other approaches.

extra properties in separate build script

I got used to having versions defined in separate dependencies.gradle file. We can add properties to extra properties extensions to be later read in another script file (and reused). Groovy is a dynamic language and allows for referring those values by names:

ext {
appCompatVersion = '1.1.0'
}
dependencies {
implementation "androidx.appcompat:appcompat:$appCompatVersion"
}

Kotlin on the other hand, is statically typed so it won’t allow for such shortcuts. This is unfortunate for our needs, but because of the same reason, we gain access to such features like refactor or auto-complete in the IDE.

Lets see how we can achieve similar result when using Kotlin scripts:

Step 1: Define values in versions.gradle.kts and put them into extra

mapOf(
"appCompatVersion" to "1.1.0"
).forEach { (name, version) ->
project.extra.set(name, version)
}

Step 2: Refer to it in any build.gradle.kts file

apply(from = "../versions.gradle.kts")
val appCompatVersion: String by extra
dependencies {
implementation("androidx.appcompat:appcompat:$appCompatVersion")
}

In order for it to work we need to declare local val property and match its name with the name given as key for extra properties extension. Luckily, extra has delegated property defined already, so we don’t have to deal with manually scanning map structure by its key. One important note here is that the property need to have String type declared, or else it won’t compile.

Unfortunately, if there are a lot of dependencies in particular module, there will be a lot of local properties defined. On top of that we need to apply versions script file. And the worst part: because we are referring to defined versions by key names which are Strings, IDE won’t help us with navigating to associated values 😢.

Full picture of necessary changes can be reviewed in this sample commit.

read from properties file

Another approach we can take is to put our version values into properties file so they can be easily accessed:

Step 1: Define values in gradle.properties

appCompatVersion=1.1.0

Step 2: Refer to it in any build.gradle.kts file

val appCompatVersion: String by projectdependencies {
implementation("androidx.appcompat:appcompat:$appCompatVersion")
}

Again, we can’t simply refer the property directly by its name. This time we use project delegated property to read values from gradle.properties. In order for it to work, local val property name must match with the one defined in properties file, and be of type String. In this approach we can skip apply from so one line less to write.

If you’re wondering whether to put the values in a separate properties file (i.e. versions.properties) that is possible, but would require (small) additional logic for reading the file.

Required changes in this approach can be reviewed in this sample commit.

Summary

Those are definitely not the only options you have. Just those which came into my mind when playing with Gradle scripts after migrating to Kotlin DSL. I won’t decide which approach is the best for you, but Gradle engineers recommends to put such values in buildSrc.

As a general rule: use the one that makes the most sense for your project. Look for good compromise between less code lines and maintainable, clean code structure. No matter which approach you will take, to me it is very joyful experience to have IDE support and more understanding of scripts syntax.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Responses (3)

What are your thoughts?