A Comprehensive Guide for Upgrading to the Gradle Kotlin DSL for Android Apps.

Mark Dappollone
5 min readMay 26, 2023

Well, it’s the time again. And by “that time,” I of course mean the time when Google decides that since no want actually wants to use their tools, you now have to use their tools. And the tool — this time around — is the altogether incrutable Gradle Kotlin DSL.

Back Up a Sec

First off let’s define stuff. A DSL is a “Domain Specific Language” or, put another way, a language that you can only use for a very specific thing. For a while, the Gradle build system that is now standard for Android apps used a DSL based in the scripting language Groovy, which you might only know about from building Android apps, because it is used for literally nothing else in the world.

You had one job.

Luckily, the folks at Gradle decided to add support for a new version of their DSL, one based in Kotlin. This does not in any way mean that Gradle build scripts are now written in Kotlin, only that the obtuse black box of Gradle build scripts now uses the val keyword, instead of def. And we’re all really excited about that, especially because it means we get to…

Learn a Completely New Build Scripting Language

And yet.

Let’s be honest: the Gradle Kotlin DSL is a totally new language, with almost every closure that exists in your gradle build file requiring an update to something very slightly different in your new build file. So here, for the first time, we’ll go through all of it — the entire file, block by block, and show you how to convert from Groovy to Kotlin — or more accurately, from the gibberish you were using before to the gibberish we’re all supposed to use now.

Steps 1

Before you do any of the below, make sure to make a copy of your build.gradle file, and rename it to build.gradle.kts. Also go through the whole thing and replace any single quotes, with double quotes.

Plugins

First, the plugins block. This one isn’t too bad. In Groovy, this might have looked like this:

plugins {
id "com.xfinity.resourceprovider" version "1.5.0"
id "com.android.application"
id "kotlin-parcelize"
id "kotlin-android"
id "kotlin-kapt"
id "dagger.hilt.android.plugin"
id "com.google.gms.google-services"
id "com.google.firebase.crashlytics"
}

apply from: "metrics.gradle"

And in Kotlin:

plugins {
id("com.xfinity.resourceprovider") version "1.5.0"
id("com.android.application")
id("kotlin-parcelize")
id("kotlin-android")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
}

apply(from = "metrics.gradle")

Notice that you need parenthees for the plugin, but not for the version. Also, the apply statement needs parentheses, but the from is named with an equals sign.

This is because nothing makes sense and it’s all made up.

Extras

If you were using extras before, strap in, there’s no way you’ll guess how to convert that.

In Groovy:

ext {
appId = "com.my.app"
}

In kotlin

extra.set("appId", "com.my.app")    

Why did they change the name of the field? Because it wasn’t confusing enough yet!

Android Block

The Android block absolutely sucks to convert. This part is not comprehensive, but I’ll show you all the blocks I had to convert, and what they wound up as in the end.

Also a fun side note is that almost everything in this block is marked in the DSL as being “incubating” or “unstable” so it’s not a terrible idea to make this The first line of your build.gradle.kts:

@file:Suppress("UnstableApiUsage")
The performance you’ve come to expect from Android tools.

In Groovy:

    lintOptions {
abortOnError false
}

In Kotlin

lint {
abortOnError = false
}

In Groovy

    defaultConfig {
applicationId "com.my.app"
minSdkVersion 23
targetSdkVersion 33
versionCode 1
versionName "v1"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [
"appAuthRedirectScheme": "com.some.redirect"
]
}

In Kotlin — read carefully because the names of a lot of the parameters is very slightly different.

defaultConfig {
applicationId = "com.my.app"
minSdk = 23
targetSdk = 33
versionCode = 1
versionName = "v1"
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["appAuthRedirectScheme"] = "com.some.redirect"
}

Signing Configs

Yes, I know you’re not supposed to spec signing configs in the build file, but if you already do and you don’t feel like changing that, then change your Groovy

    signingConfigs {
release {
storeFile file("..\\dir\\keyfile.jks")
storePassword "storepass"
keyAlias "mykey"
keyPassword "keypass"
}
config {
enableV4Signing = true
}
}

to Kotlin

signingConfigs {
create("release") {
keyAlias = "mykey"
keyPassword = "keypass"
storeFile = file("..\\dir\\keyfile.jks")
storePassword = "storepass"
enableV4Signing = true
}
}

Build Types

in Groovy

    buildTypes {
debug {
debuggable true
}
release {
minifyEnabled true
shrinkResources true
debuggable false
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}

In Kotlin

buildTypes {
getByName("debug") {
isDebuggable = true
}
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
isDebuggable = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
}
}

Build Variants/Flavors

In Groovy

    flavorDimensions "version"
productFlavors {
qa {
dimension "version"
applicationId "com.my.app.qa"
buildConfigField "String", "appName", "My QA App"
}
prod {
dimension "version"
applicationId "com.my.app.prod"
buildConfigField "String", "appName", "My Prod App"
}
}

In Kotlin

flavorDimensions.add("version")
productFlavors {
create("qa") {
dimension = "version"
applicationId = "com.my.app.qa"
buildConfigField("String", "appName", "My QA App")
}
create("prod") {
dimension = "version"
applicationId = "com.my.app.prod"
buildConfigField("String", "appName", "My Prod App")
}
}

Output File Names

Here’s something we’re all already doing in Groovy — renaming the output files:

    applicationVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = new File("myapp-" + variant.flavorName + "-" + variant.versionName + ".apk")
}
}

In Kotlin

applicationVariants.all {
val variant = this
variant.outputs.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
val outputFileName = "myapp-${variant.flavorName}-${variant.versionName}.apk"
output.outputFileName = outputFileName
}
}

The Rest

I’m getting lazy. Here’s the rest of the Android block

In Groovy:

    compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = "1.8"
}
packagingOptions {
exclude "META-INF/*.kotlin_module"
exclude "META-INF/DEPENDENCIES"
}
testOptions {
unitTests.includeAndroidResources = true
unitTests.returnDefaultValues = true
}

kapt {
correctErrorTypes true
}

And in Kotlin:

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = true
}

kotlinOptions {
jvmTarget = "1.8"
}

packagingOptions {
resources {
excludes += listOf("META-INF/*.kotlin_module","META-INF/DEPENDENCIES")
}
}
testOptions {
unitTests.isIncludeAndroidResources = true
unitTests.isReturnDefaultValues = true
}

kapt {
correctErrorTypes = true
}

Dependencies

Pretty straightforward for once:

In Groovy

dependencies {
implementation "androidx.appcompat:appcompat:1.6.1"
}

In Kotlin

dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
}

Phew

Alright that’s about it. If you’ve written any custom Groovy code, you’ll have to convert it to Kotlin, but you can also port that out to a plugin. And that’s what we’ll talk about in the next article.

--

--

Mark Dappollone

Director, Mobile Product Engineering at Anywhere Real Estate