How to Convert Android Gradle Groovy to KTS?

Step-by-step guide to convert or migrate your Android project's Gradle script from Groovy script to Kotlin script (KTS)

ยท

5 min read

How to Convert Android Gradle Groovy to KTS?

Introduction

Gradle is the build automation tool. Android Studio uses Gradle to build the apps. Gradle can be written in these 2 domain-specify languages (DSL):

  • Groovy Script

  • Kotlin Script (KTS)

What is Groovy?

Groovy is a dynamically typed language, which means the variable types are known at run time. This is usually an interpreter or scripting language.

What is Kotlin?

Kotlin is a statically typed language, which means the variable types are known at compile time. This is usually for general-purpose programming language. However, for this Gradle building context, Kotlin is considered as a script. It is called Kotlin Script (KTS)

Both Groovy and Kotlin can run on Java Virtual Machine (JVM).

Why Convert Groovy to KTS?

This is what official documentation says:

In the future, KTS will be preferred over Groovy for writing Gradle scripts because Kotlin is more readable and offers better compile-time checking and IDE support.

But it also says:

builds using KTS tend to be slower than builds using Groovy

It sounds to me, it is not ready yet? Probably that's the reason why the default Gradle script setup from Android Studio is Groovy and not KTS.

If build performance is not an issue, maybe you can consider migrating to KTS? But who doesn't want a faster build?

So I think conversion to KTS is probably for education purposes or preparing yourself for the future. The following guide provides step-by-step instructions on what I did to convert my template app to use KTS.

Step-by-step Guide

  • *.gradle extension is for Groovy script

  • *.gradle.kts extension is for Kotlin script.

1. Rename settings.gradle to settings.gradle.kts

You get the following error.

Function invocation 'include(...)' expected

To fix that, you change

include ':app'

to

include ("app")

You get this message on the top of your IDE.

Multiple script definitions are applicable to this script. KotlinSettingScript is used

It looks like a known issue, but it is not a show-stopper.

You also have this message which will be gone after you convert all the Groovy scripts to KTS.

Code insight unavailable (script configuration wasn't received) - Add to standalone scripts

2. Rename build.gradle to build.gradle.kts

You get this error:

Unexpected tokens (use ';' to separate expressions on the same line)

To fix that, you change

task clean(type: Delete) {
    delete rootProject.buildDir
}

to

tasks {
    register("clean", Delete::class) {
        delete(rootProject.buildDir)
    }
}

[Updated - Jan 3, 2023]: It looks like this "clean" task is no longer needed because it has been implemented by default in Gradle. So I have removed this task in build.gradle / build.gradle.kts.

After that, you compile and get the same error:

Unexpected tokens (use ';' to separate expressions on the same line)

To fix that, you replace

buildscript {
    ext {
        android_gradle_plugin_version = '7.2.2'
    }
}

plugins {
    id 'com.android.application' version "$android_gradle_plugin_version" apply false
    id 'com.android.library' version "$android_gradle_plugin_version" apply false
    id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
}

with

buildscript {
    val android_gradle_plugin_version by extra("7.2.2")
}

plugins {
    id("com.android.application") version "${extra["android_gradle_plugin_version"]}" apply false
    id("com.android.library") version "${extra["android_gradle_plugin_version"]}" apply false
    id("org.jetbrains.kotlin.android") version "1.7.0" apply false
}

It can build and run successfully. However, there is still this error message:

val ExtensionAware.extra: ExtraPropertiesExtension' can't be called in this context by implicit receiver. Use the explicit one if necessary

It looks like this is plugins DSL limitations - constrained syntax which doesn't allow you to access the variable in the plugins block - does not support arbitrary code.

The plugins {} block does not support arbitrary code. It is constrained...

So just hard code it instead:

buildscript {
}

plugins {
    id("com.android.application") version "7.2.2" apply false
    id("com.android.library") version "7.2.2" apply false
    id("org.jetbrains.kotlin.android") version "1.7.0" apply false
}

FYI - I don't have the compose_version variable here because I've moved it to the app\build.gradle

3. Rename app\build.gradle to app\build.gradle.kts

If you use the "Refactor -> Rename...", it automatically updates the comment in proguard-rules.pro file.

You will get errors, and here are the fixes.

Change plugins

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

to

plugins {
    id ("com.android.application")
    id ("org.jetbrains.kotlin.android")
}

Change compile Sdk & defaultConfig

android {
    compileSdk 32

    defaultConfig {
        applicationId "vtsen.hashnode.dev.newemptycomposeapp"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }
    /*...*/
}

to

android {
    compileSdk = 32

    defaultConfig {
        applicationId = "vtsen.hashnode.dev.newemptycomposeapp"
        minSdk = 21
        targetSdk = 32
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
    }
    /*...*/
}

Change buildTypes and compleOptions

android {
    /*...*/
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'), 
                    'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    /*...*/
}

to

android {
    /*...*/
    buildTypes {
        release {
            isMinifyEnabled  = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                    "proguard-rules.pro")
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    /*...*/
}

Note: minifyEnabled is renamed to isMinifyEnabled

Change buildFeatures and composeOptions

android {
    /*...*/
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.2.0'
    }
    /*...*/
}

to

android {
    /*...*/
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.2.0"
    }
    /*...*/
}

Change packagingOptions and namespace

android {
    /*...*/
    packagingOptions {  
        resources {  
            excludes += '/META-INF/{AL2.0,LGPL2.1}'  
        }  
    }

    namespace 'vtsen.hashnode.dev.newemptycomposeapp'
}

to

android {
    /*...*/
    packagingOptions {  
        resources {  
            excludes.add("/META-INF/{AL2.0,LGPL2.1}")  
        }  
    }

    namespace = "vtsen.hashnode.dev.newemptycomposeapp"
}

Change dependencies

dependencies {
    implementation 'androidx.core:core-ktx:1.8.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
    implementation 'androidx.activity:activity-compose:1.5.1'

    def compose_version = '1.2.1'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"

    implementation "com.google.accompanist:accompanist-systemuicontroller:0.24.2-alpha"
}

to

dependencies {  

    implementation("androidx.core:core-ktx:1.8.0")  
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")  
    implementation("androidx.activity:activity-compose:1.5.1")  

    val composeVersion = "1.2.1"  
    implementation("androidx.compose.ui:ui:$composeVersion")  
    implementation("androidx.compose.material:material:$composeVersion")  
    implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")  
    debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion") 

    implementation("com.google.accompanist:accompanist-systemuicontroller:0.24.2-alpha")  
}

composeVersion is used instead of compose_version due to the naming convention in Kotlin

Build Time Comparisons

I perform a quick built-time testing, the difference is not significant. Both build times are about the same. Probably this project is too simple, so the difference is not obvious.

Build TimeFirst Compile (clean build)Second Compile (clean build)
Groovy6 seconds2-3 seconds
KTS6 seconds2-3 seconds

Source Code

Did you find this article valuable?

Support Vincent Tsen - AndroidDev Blog by becoming a sponsor. Any amount is appreciated!

ย