Multi-module Android project with Kotlin DSL for Gradle

Andy Wang
3 min readFeb 13, 2020

--

Android + Kotlin + Gradle

Recently, I decided to convert one of my multi-module projects to use Kotlin DSL for Gradle. The goal is to extract common configurations as much as possible so that the build script for each module is very simple. For example, to create a new module in this project, I don’t need to copy-paste a lot of common configurations from the existing build files.

The project consists of 3 modules.

  • app: Android application module
  • data: Android library module
  • domain: Android library module

The following shows how the build.gradle files look like for the data and domain module.

build.gradle files

After converting to Kotlin DSL and refractory, the 2 build.gradle.kts files shrank more than 60%.

build.gradle.kts files

Custom plugins with Kotlin DSL

Custom Gradle plugins are a great way to share common build logic. But writing and publishing a standalone Gradle plugin could have a lot of overhead involved. There is a great place to put custom plugins or code which are compiled and included in the build script. The Gradle manual describes buildsrc project as

buildSrc project

You can put the source for the plugin in the rootProjectDir/buildSrc/src/main/groovy directory (or rootProjectDir/buildSrc/src/main/java or rootProjectDir/buildSrc/src/main/kotlin depending on which language you prefer). Gradle will take care of compiling and testing the plugin and making it available on the classpath of the build script. The plugin is visible to every build script used by the build. However, it is not visible outside the build, and so you cannot reuse the plugin outside the build it is defined in.

Kotlin DSL also makes writing and applying custom Gradle plugins much easier. For example, to define a plugin calledandroid-base, we only need to create a android-base.gradle.kts file in the buildsrc folder.

// buildsrc/android-base.gradle.ktsplugins {
kotlin("android.extensions")
kotlin("kapt")
}
dependencies {
implementation(Dependencies.TIMBER)
implementation(Dependencies.COROUTINES_ANDROID)
}

Let’s say we have 2 pluginsandroid-base.gradle.kts and android-base-lib.gradle.kts in the buildsrc folder. To apply them, we just need to use the backtick notation. For example

// module/build.gradle.ktsplugins {
`android-base-lib`
`android-base`
}

Grouping dependencies

Sometimes we have dependencies that always go together. It is better to group them and apply them all in one statement. First, we define all the dependencies and their version in a separate file. For example

// buildsrc/{package}/dependencies.ktobject Dependencies {const val DAGGER = "com.google.dagger:dagger:${Versions.DAGGER2}"
const val DAGGER_COMPILER = "com.google.dagger:dagger-compiler:${Versions.DAGGER2}"
const val DAGGER_ANDROID = "com.google.dagger:dagger-android-support:${Versions.DAGGER2}"
const val DAGGER_ANDROID_PROCESSOR = "com.google.dagger:dagger-android-processor:${Versions.DAGGER2}"
}object Versions {const val DAGGER2 = "2.26"}

Now we want to apply the Dagger dependencies in a group. One way of doing that is defining an extension function on the DependencyHandler.

// buildsrc/{package}/dependencies.ktfun DependencyHandler.dagger() {
compileOnly(Dependencies.DAGGER)
kapt(Dependencies.DAGGER_COMPILER)
implementation(Dependencies.DAGGER_ANDROID)
kapt(Dependencies.DAGGER_ANDROID_PROCESSOR)
}
private fun DependencyHandler.implementation(depName: String) {
add("implementation", depName)
}
private fun DependencyHandler.kapt(depName: String) {
add("kapt", depName)
}
private fun DependencyHandler.compileOnly(depName: String) {
add("compileOnly", depName)
}
private fun DependencyHandler.api(depName: String) {
add("api", depName)
}

Because DependencyHandler is the default context inside the scope, we can directly call the extension functions in the scope. For example, in the build.gradle.kts in any module

// module/build.gradle.ktsdependencies {
// Dagger Android dependencies
dagger
()
}

I am sure there are improvements to the build script as I am not an expert in Gradle. Please leave your comments for any feedback. I’d also recommend having unit tests for your build script, custom plugins as they get more complicated.

All the files used in the article can be found in this Gist.

If you enjoy reading this post, please applaud using the 👏 button and share it through your circles. Thank you.

--

--