Kotlin DSL: Gradle scripts in Android made easy
By: Vikas Kumar
Tags: Gradle Kotlin Dsl, Kotlin, Gradle Plugin, Android App Development, Kotlin Dsl

If you are scared of Gradle scripts with unfamiliar groovy syntax files, then Kotlin DSL is made for you. Its time to face the Gradle scripts with more confidence. In this article, we will convert some common Groovy-based Gradle scripts into Kotlin DSL scripts.

Let’s get started

Open half baked side android project 😉 of yours. In your project-level root folder create a folder named buildSrc same as that of your app/ folder in your project. This is a special folder to enable the Gradle scripts at the project level. Hit the sync button and you will see how these changes and adds some temporary files inside it.

buildSrc folder

Half the battle is done 😀 now in order to enable the Kotlin DSL we gotta do something more here. So put our lazy brain to work and create a file inside buildSrc naming it build.gradle.kts open now the newly created file to add some code that tells Gradle to enable the Kotlin-DSL plugin for our project.

build.gradle.kts

Sync now, That’s it Battle is won, and Kotlin DSL is enabled in our project.

Is there anything left to do? 🤔

Yes, whatever we have done is of no use until we put Kotlin DSL into some action.

Managing dependencies in large android projects is still cumbersome and hectic and that unfamiliar groovy syntax makes it even harder to understand the changes.

Kotlin DSL brings the simplicity of the Kotlin language syntax and rich API set right into the script files on top of that code completion makes it perfect to work with Gradle script files. We will use this to manage our dependencies and project settings configurations more elegantly. Kotlin DSL files have extension as .kts so we will be converting all of our .gradle files to .gradle.kts

Before converting our files to Kotlin DSL let’s have some necessary files in place in order to go ahead. Create folders inside the buildSrc folder as below and create the stated files.

buildSrc->src->main->java

AppConfig.kt:

This file helps us manage our app-level configurations related to the project at once place.

Versions.kt

This file helps us separate our versioning of the libraries in one place.

AppDependencies.kt

This file holds all the dependencies of our app related to UI, test, and other third-party libraries. Apart from that this also holds the Kotlin extension functions for implementation, testImplementation, androidTestImplementation, kapt which basically now accepts a list of String (dependencies) which is helpful for adding multiple dependencies at once instead of adding one by one in build.gradle file. You can play around and separate dependencies based on the module name also by creating a different list of dependencies for your module and add all at once by using a single line of code Gradle script.

Here is how it will look like when we are done adding all the Kotlin files.

Also, it’s no hard and fast rule we can also manage all these constants in one file also but if your project is large enough to make you go mad for a single change then it’s better to have separate the concerns in different files for different purposes. Let’s convert

settings.gradle

Our existing code for settings.gradle file as below

settings.gradle.kts

include is now just a function taking vararg of String to include the modules in the project. Moving next we gonna change our project level build.gradle file

build.gradle

build.gradle.kts

Kotlin DSL version is almost the same since it’s using DSL to map those syntaxes and tries to be as close as possible for example.

classpath("com.android.tools.build:gradle:${Versions.gradle}")

classpath is just a regular Kotlin function taking String as input. Which we all know and understand and can relate the syntax. Let’s see how our main app-level build.gradle file changes to build.gradle.kts

Plugins are the first thing in the main app-level Gradle file which enables the android support in regular Intellij project or Kotlin support in an android project or any third party plugin which needed at module level can be applied in this file, we will see show that changes

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

will be changed to

plugins {

id("com.android.application")

kotlin("android")

kotlin("android.extensions")

}

or you can use another way of adding plugins as shown below, the above one is the DSL way of doing things.

apply(plugin = "com.google.firebase.crashlytics")

now let’s try to go chunk by chunk for an android block with the basic setup.

android {

compileSdkVersion 30

buildToolsVersion "29.0.3"



defaultConfig {

applicationId "com.vikas.hiltandroid"

minSdkVersion 21

targetSdkVersion 30

versionCode 1

versionName "1.0"



testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

}



//......

}

will be changed to

android {

compileSdkVersion(AppConfig.compileSdk)

buildToolsVersion(AppConfig.buildToolsVersion)



defaultConfig {

applicationId = "com.vikas.hiltandroid"

minSdkVersion(AppConfig.minSdk)

targetSdkVersion(AppConfig.targetSdk)

versionCode = AppConfig.versionCode

versionName = AppConfig.versionName



testInstrumentationRunner = AppConfig.androidTestInstrumentation

}



//......

}

Now it’s possible to directly access the Kotlin object constants or any other constants like as shown above. Here we are using the earlier created constants in file AppConfig for the app level configurations. Next, let’s see how we can create the debug and release versions.

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

}

}

will be changed to

buildTypes {

getByName("release") {

isMinifyEnabled = false

proguardFiles(

getDefaultProguardFile("proguard-android-optimize.txt"),

"proguard-rules.pro"

)

}

}

you don’t have to create debug because it’s there by default until unless needed to change some properties. If so then do something like this.

getByName("debug") {

isMinifyEnabled = false

}

flavors:

flavorDimensions(AppConfig.dimension)

productFlavors {

create("staging") {

applicationIdSuffix = ".staging"

setDimension(AppConfig.dimension)

}



create("production") {

setDimension(AppConfig.dimension)

}

}

viewbinding:

viewBinding {

android.buildFeatures.viewBinding = true

}

coming to the main dependencies this is how it was like before

dependencies {

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

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

implementation 'androidx.core:core-ktx:1.3.1'

implementation 'androidx.appcompat:appcompat:1.1.0'

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

testImplementation 'junit:junit:4.12'

androidTestImplementation 'androidx.test.ext:junit:1.1.1'

androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'



}

will be now changed to below

dependencies {

//std lib

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

//app libs

implementation(AppDependencies.appLibraries)

//test libs

testImplementation(AppDependencies.testLibraries)

androidTestImplementation(AppDependencies.androidTestLibraries)

}

this is quite maintainable, easy to read & understand.

putting all this together will look like as below.

If you have any android module, conversion goes almost the same for the modules as well instead of using an android id plugin use library plugin id.

Kotlin DSL is not limited to the usages across Gradle scripts it’s way broader than just scripting. Let’s go and explore this side of Kotlin DSL by putting this into implementation.

Styling TextView using Kotlin DSL.

Let’s start by creating the backbone which will support the DSL.

in our MainActivity we will use this as follow.

Output:

This is the very basic example of Kotlin DSL, we can write to express almost anything using this. This is a much more clean and expressive way of doing programming. Since it uses Kotlin lambda expressions extensively to achieve this, we should be conscious of the extra classes it generates at the byte code level. So how to fix this 🤔, simply try to inline the lambda expressions where ever possible in order to reduce the extra class which would get generated for the small code blocks.

That’s it folks for today. Please be generous enough to comment for feedback or suggestions or any queries. Thanks for reading 😊.

References/further read: