Step-by-step guide to convert or migrate your Android project's Gradle script from Groovy script to Kotlin script (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 theapp\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 toisMinifyEnabled
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 ofcompose_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 Time | First Compile (clean build) | Second Compile (clean build) |
---|---|---|
Groovy | 6 seconds | 2-3 seconds |
KTS | 6 seconds | 2-3 seconds |
Source Code
GitHub Repository: Demo_CleanEmptyCompose
Conversion: Diff
Originally published at https://vtsen.hashnode.dev.
Top comments (4)
A build operation failed.
Could not create task ':app:processDebugResources'.
Could not create task ':app:processDebugResources'.
Cannot use @TaskAction annotation on method IncrementalTask.taskAction$gradle_core() because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method.
Mine is okay. It could be due to the older version of Android Studio. I'm on Android Studio Flamingo.
Thanks, this article save me!!
I'm glad it helps.