December 10, 2018
6 minutes
Migrating Gradle Build Scripts to Kotlin DSL
Howdy people! It’s bean a while since Gradle released support for Kotlin scripts. We have been writing Gradle script in a language called Groovy since ages. Let’s be honest, We all know that Groovy sucks! Half of the times I have been like this when there’s an issue:
Why does Groovy suck?
- No Auto Completion🙄
- No Content Assist😞
- No Navigation to Source😕
- Don’t know what to write and how to write😪
- I don’t like it😠
Kotlin Script to The Rescue
Gradle released support for Kotlin scripts and Kotlin DSL(In short, DSL makes code more readable, it’s built in Kotlin feature). Finally, I don’t have to learn that lame Groovy for Gradle scripts. Now I can write my build scripts in my favorite language Kotlin. When it released, I was like:
I never learned Groovy but now, all I have to do is to be yourself and write Kotlin scripts like a PRO. How cool is that! A single language to manage your whole project. Kotlin script overcomes almost all the cons of Groovy.
Why Kotlin scripts are awesome:
- Auto Completion😎
- Content Assist😍
- Navigation to Source😱
- Kotlin Extensions support😘
- Errors at compile time instead of runtime💪
- I love it❤️
Writing build scripts in Kotlin is like riding a bike🏍 (Except the bike is on fire🔥).
Sometimes it can be a bit messy to rewrite gradle.build into gradle.build.kts files, especially when all its cache is malfunctioning during that process. Few times I had to reopen my project so that Android Studio can understood properly what is going on. “Refresh all Gradle projects” button has been life saver for me.😉
But don’t worry, Just follow the steps with me and it will be fine.
Migrate to Gradle Kotlin Script
A nice thing about Kotlin build script is you don’t have to do extra setup for enabling it. Just rename .gradle file to .gradle.kts file extension and Gradle will consider it as a Kotlin script. Please note that following the steps below in sequence is highly important to avoid intermediate Gradle issues.
Gradle support
To make sure that everything works fine, update your gradle to version 4.10. To do so, go to your project’s gradle-wrapper.properties and check distributionUrl for the current version. make sure it looks like below:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
Step-1 Convert Settings.gradle File
Let’s start with settings.gradle file by renaming (Shift + F6) it to settings.gradle.kts. Now that this file is a Kotlin script, include will behave as a function and ":app" will be the string argument.
Before
include ':app'
After
include(":app")
✏️Note that in Groovy, you were able to write single quotes('') in place of double quotes("") but in Kotlin, you must have to use double quotes only.
Now that you have converted it in Kotlin, Gradle will identify it accordingly and will process it as Kotlin script. Just sync with Gradle files.
Step-2 Convert Project’s build.gradle File
Rename project level build.gradle to build.gradle.kts. The main difference here is in classpath and in clean task.
Classpath is now a function in Kotlin which takes a string argument as shown below:
Before
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21"
}
After
dependencies {
classpath("com.android.tools.build:gradle:3.3.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21")
}
Clean task will no longer be written in Groovy. This is how it looks in Kotlin:
Before
task clean(type: Delete) {
delete rootProject.buildDir
}
After
tasks.register("clean",Delete::class){
delete(rootProject.buildDir)
}
Another thing that can change is that if you have any repositories with url in repositories block. You can change it like this:
Before
repositories {
google()
jcenter()
maven{ url 'https://jitpack.io' }
}
After
repositories {
google()
jcenter()
maven { url = uri("https://jitpack.io") }
}
That’s it! The file is now completely in Kotlin. If you have some other configurations then you can easily convert it into Kotlin with the help of Auto Completion. If you don’t know how to implement something, you can always navigate to the source code. That’s the beauty of it.
Here is the whole build.gradle.kts file:
buildscript {
repositories {
maven { url = uri("https://jitpack.io") }
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:3.3.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21")
}
}
allprojects {
repositories {
google()
jcenter()
maven { url = uri("https://jitpack.io") }
}
}
tasks.register("clean",Delete::class){
delete(rootProject.buildDir)
}
You’ll be able to sync your project after completing this step.
Step-3 Convert App Level build.gradle File
Here comes a little bit messy part😵. Open app level build.gradle file and rename it to build.gradle.kts. Don’t try to sync now. It will show lots of errors but don’t worry, we’re going to remove all the errors one by one. Let’s start from the top.
In Groovy, every plugin was being applied individually where as Kotlin provides plugins{} block to apply all the plugins.
Before
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
After
plugins {
id("com.android.application")
kotlin("android")
kotlin("android.extensions")
}
Here, id() and kotlin() are functions which are used to apply plugins.
id() is used to apply any plugin. All the plugins can be applied using id().
Plugins which are specific to Kotlin can also be applied using kotlin() function. That’s cool😎
Note that when you use kotlin() function, the kotlin prefix will be removed from plugin name and the dash(-) will be replaced by dot(.). For the sake of simplicity, you can use id() function instead.
The “android{}” Block
Note these points to convert this block in Kotlin:
- compileSdkVersion is a function.
- applicationId is a property.
- minSdkVersion and targetSdkVersion are functions too.
- versionCode and versionName are properties again.
According to above points this block will look like this:
Before
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.birjuvachhani.gradlekotlindsldemo"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
...
}
After
android {
compileSdkVersion(28)
defaultConfig {
applicationId = "com.birjuvachhani.gradlekotlindsldemo"
minSdkVersion(19)
targetSdkVersion(28)
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
...
...
}
buildTypes{} block is a bit tricky. A function getByName(String) is used to get a build type.
minifyEnabled is a property with name isMinifyEnabled.
proguardFiles and getDefaultProguardFile are functions.
This is how it will look like:
Before
android {
...
...
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
After
android {
...
...
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
The dependencies{} Block
There should be only one block left (if you don’t have any extra configs hopefully😬) that is the dependencies{} block.
Here, implementation, kapt, api, testImplementation, androidTestImplementation etc are functions.
The fileTree in implementation() is also a function which takes a map as an argument. Here’s how it will look like:
Before
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21"
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
implementation 'androidx.core:core-ktx:1.1.0-alpha04'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test🏃♂️1.1.2-alpha01'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.2-alpha01'
}
After
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21")
implementation("androidx.appcompat:appcompat:1.1.0-alpha02")
implementation("androidx.core:core-ktx:1.1.0-alpha04")
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
testImplementation("junit:junit:4.12")
androidTestImplementation("androidx.test🏃♂️1.1.2-alpha01")
androidTestImplementation("androidx.test.espresso:espresso-core:3.1.2-alpha01")
}
Here is the whole app level build.gradle.kts file:
plugins {
id("com.android.application")
kotlin("android")
kotlin("android.extensions")
}
android {
compileSdkVersion(28)
defaultConfig {
applicationId = "com.birjuvachhani.gradlekotlindsldemo"
minSdkVersion(19)
targetSdkVersion(28)
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro")
}
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21")
implementation("androidx.appcompat:appcompat:1.1.0-alpha02")
implementation("androidx.core:core-ktx:1.1.0-alpha04")
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
testImplementation("junit:junit:4.12")
androidTestImplementation("androidx.test🏃♂️1.1.2-alpha01")
androidTestImplementation("androidx.test.espresso:espresso-core:3.1.2-alpha01")
}
Hell Yeah! Sync the project (Hopefully it will compile, let’s have some faith in Gradle). There you go, your project is converted in Kotlin build scripts. Say goodbye to Groovy!
If you’re reading this then you’d be probably like this:
or this:
It depends on you.😄 It’s just a matter of time, Gradle will provide kotlin script support by default. That means this all will be directly generated for you. Till then, Happy coding folks!