Hi, I'm Tiago

Dependency management with Gradle and Kotlin DSL

Published on

Managing several libraries in a multi-module project might get a bit complicated over time. Hopefully, there’s Gradle Kotlin DSL for the rescue.

In your app/build.gradle for an Android module, you might have:

dependencies {
    //...
    implementation ("androidx.appcompat:appcompat:1.0.2")
    //...
}

After a few months, you decide to create a new library module and move some nice library you’ve been writing in your main module to a separate one. Then you create your new module using the assistant in Android Studio, and it creates a new mylib/build.gradle file that’s going to include:

dependencies {
    //...
    implementation ("androidx.appcompat:appcompat:1.2.0")
    //...
}

You end up having multiple versions of the same library, slowing down your build and increasing your apk size. It’s very easy to miss that duplicates once you have a dozen libraries in each module, or even if you’re not actively paying attention to library versions at the moment you’re editing a build file.

One alternative when you’re using Gradle Kotlin DSL, is to have a separate file with all your dependencies and import them in the modules you need. I like to have it like this:

// file: buildSrc/src/main/kotlin/Deps.kt

object Deps {
    object AndroidX {
        val appCompat = "androidx.appcompat:appcompat:1.3.0-beta01"
        val constraintLayout = "androidx.constraintlayout:constraintlayout:2.1.0-alpha2"
        val recyclerView = "androidx.recyclerview:recyclerview:1.2.0-beta01"
    }

    object Firebase {
        val analytics = "com.google.firebase:firebase-analytics:17.4.4"
        val crashlytics= "com.google.firebase:firebase-crashlytics:17.1.1"
    }

    object Google {
        val material = "com.google.android.material:material:1.3.0"
        val gson = "com.google.code.gson:gson:$2.8.6"
    }

    // other deps
    object Retrofit {
        private val retrofitVersion = "2.9.0"
        private val okhttpVersion = "3.14.3"

        val retrofit = "com.squareup.retrofit2:retrofit:$retrofitVersion";
        val gsonConverter = "com.squareup.retrofit2:converter-gson:$retrofitVersion";

        val okhttp = "com.squareup.okhttp3:okhttp:$okhttpVersion"
        val loggingInterceptor = "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
    }
}

Having all dependencies in the same file makes it easier to avoid repeating yourself and being sure the versions of the libraries you have are compatible with one another.

This allows you to do in your module’s build.gradle.kts:

//file: app/build.gradle.kts
//...

dependencies {
    implementation(Deps.AndroidX.appCompat)
    implementation(Deps.AndroidX.constraintLayout)
    //...
}

Or, if you rather:

//file: app/build.gradle.kts
//...

dependencies {
    with (Deps.AndroidX) {
        arrayOf(
            appCompat,
            constraintLayout,
            recyclerView
        ).forEach { implementation(it) }

    }
    //...
}

If you’re not yet familiar with Gradle Kotlin DSL or if you’re not yet convinced of the advantages of using Kotlin over Groovy to write build files, maybe you want to check you the Migrating build logic from Groovy to Kotlin guide in the official Gradle documentation.

If you’d like to see a simple yet complete of an Android project that uses Gradle Kotlin DSL, you may check my Gradle Notes repository in GitHub.