Skip to main content

Manage your Gradle dependencies in Kotlin, even from Groovy scripts

Updated:

Deprecated

Gradle now includes Version Catalogs, allowing dependency coordinates and versions to be shared between subprojects. On top of this, Gradle generates a type-safe API to reference these dependencies in your build scripts. It is recommended to use Version Catalogs instead of what is discussed in this article. This article will no longer be maintained, meaning the code samples may become out-of-date. Please check out my latest articles for more up-to-date topics.

Find out more

When defining the dependencies for a Gradle project, such as an Android app, there are situations that call for duplicated information. A common case is that libraries or plugins can often come with multiple dependencies, which are usually released together with matching versions. Maintaining these dependencies requires the version to be duplicated and then kept up-to-date as new versions are released.

Another case is that it is common to have a multi-module project, where we may want a dependency to be added to multiple modules. Similarly to the case with versions, the dependency definition would be duplicated for each module and then kept in-sync moving forward.

It is clear that in both cases we may want to define the dependencies in one place and then access this location from wherever the dependency definition is needed.

The Groovy way

There is a mechanism for sharing your dependency configuration using Groovy, by adding some extra properties in our root build.gradle file, similar to below.

→ /build.gradle

buildscript {
  ext.versions [
    'kotlin': '1.3.10',
    'dagger': '2.19'
  ]

  ext.deps [
    'kotlin': [
      'stdlib': "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}",
      'test': "org.jetbrains.kotlin:kotlin-test-common:${versions.kotlin}"
    ],
    'dagger': [
      'runtime': "com.google.dagger:dagger:${versions.dagger}",
      'compiler': "com.google.dagger:dagger-compiler:${versions.dagger}",
      'androidRuntime': "com.google.dagger:dagger-android:${versions.dagger}",
      'androidCompiler': "com.google.dagger:dagger-android-processor:${versions.dagger}",
    ]
  ]
}

→ /app/build.gradle

dependencies {
  implementation deps.kotlin.stdlib

  implementation deps.dagger.runtime
  implementation deps.dagger.androidRuntime
  kapt deps.dagger.compiler
  kapt deps.dagger.androidCompiler

  testImplementation deps.kotlin.test
}

Sharing the configuration via the root build.gradle achieves the goal of having a single location to maintain. Only needing to update dependency versions in a single file is a great time-saver as well. Unfortunately from practice, when using the Groovy Gradle DSL auto-complete is often missing or incomplete in this situation, making it awkward to use.

On top of this, Gradle 5.0 is now available which includes the production-ready Kotlin Gradle DSL. When we convert the above solution to Kotlin it isn't as nice to use in Kotlin as it is in Groovy. The map syntax can be achieved using mapOf, but reading the data to pass to implementation requires some casting to fit with the statically typed language.

Kotlin to the rescue

As many developers have found with their main app source code, Kotlin can help with defining our dependency configurations as well, from both Groovy and Kotlin Gradle scripts.

Kotlin and Gradle logos

When ran, Gradle will check for a buildSrc directory in the root of the project, placing any code that is found into the classpath of our build. This is really handy and allows us to put Kotlin code into buildSrc and then reference it from our Gradle scripts. To get started we just need a little bit of structure.

→ /buildSrc/build.gradle.kts

plugins {
  `kotlin-dsl`
}

The Kotlin DSL plugin needs to be specified for buildSrc.

→ /buildSrc/src/main/kotlin/Versions.kt

Our source files need to be placed within the standard sources directory structure. To use inner objects, for now we need to include the sources within a package, e.g. 'com.ourapp'.

object Versions {
  const val dagger = "2.21"
  const val ktlint = "0.29.0"
  const val kotlin = "1.3.11"
}

→ /buildSrc/src/main/kotlin/Dependencies.kt

object Plugins {
    const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
    const val ktlint = "com.github.shyiko:ktlint:${Versions.ktlint}"
}

object Libs {
    const val kotlinStdlib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"

    const val daggerRuntime = "com.google.dagger:dagger:${Versions.dagger}"
    const val daggerCompiler = "com.google.dagger:dagger-compiler:${Versions.dagger}"
    const val daggerAndroidRuntime = "com.google.dagger:dagger-android:${Versions.dagger}"
    const val daggerAndroidCompiler = "com.google.dagger:dagger-android-processor:${Versions.dagger}"
}

Once Gradle has been synced, we can now reference our dependencies within our Gradle scripts. This is pretty much the same as we used with the Groovy extra properties solution above.

→ /app/build.gradle

dependencies {
  implementation Libs.kotlinStdlib

  implementation Libs.daggerRuntime
  implementation Libs.daggerAndroidRuntime
  kapt Libs.daggerCompiler
  kapt Libs.daggerAndroidCompiler

  ktlint Plugins.ktlint
}

Using buildSrc and Kotlin sources achieves the goal of having a single location to maintain and we can even reduce it to a single Kotlin source file if it makes sense to. The solution can be used in the same way from both Groovy and Kotlin Gradle DSL without being altered, which frees us up to change our Gradle scripts to Kotlin if we want to.

The best thing about defining dependencies in this way is that we get access to effective auto-complete and being able to click through to where a dependency is defined from our Gradle scripts, super useful.

A good tool to look into is Ben Manes' Gradle Versions Plugin, which will inform us of any dependencies with new versions. Once we have the latest versions, we can simply update them in Versions.kt.

Wrap up

Naturally, for the reasons given I prefer to use the buildSrc and Kotlin source approach in my Android projects. It is really nice to use with both Groovy Gradle scripts and those written using the Gradle Kotlin DSL.

I hope the article was useful. If you have any feedback or questions please feel free to reach out.

Thanks for reading!

Like what you read? Please share the article.

Avatar of Andrew Lord

WRITTEN BY

Andrew Lord

A software developer and tech leader from the UK. Writing articles that focus on all aspects of Android and iOS development using Kotlin and Swift.

Want to read more?

Here are some other articles you may enjoy.