Shouldn't editing a build script be as much fun as developing your app? Can't we use a language we know and like to do so? Maybe a language like Kotlin? Yes, yes we can! In this article I’ll show you how to migrate your Groovy build script to Gradle Kotlin DSL in just five simple steps.
Why?
Over the years Android app development has changed a lot. New APIs, pattern and libraries rise, shine and sometimes disappear. Furthermore, with Kotlin another programming language for Android development gained popularity. These constant changes don’t stop at the build system. The community (mostly) moved from build scripts with Apache Ant to the unofficially supported Apache Maven to Gradle. In my opinion a good choice and I prefer Gradle over the other two. But some points in using Gradle always bothered me: I use Groovy, the language for Gradle scripts, so rarely and therefore with so little knowledge that changes to the build script are often based on trial and error.
There are other reasons to move away from Groovy. It’s a dynamically typed language and hence the tooling support isn't that powerful. Features like auto-completion, navigation to the source code or reliable refactoring aren’t available or only partially supported. This isn’t an ideal situation for me and other developers have made similar arguments. Using Gradle with a statically-typed language like Kotlin would bring some advantages in terms of language knowledge and tooling.
The Kotlin DSL
The Kotlin DSL provides everything required to write Gradle build scripts with Kotlin. Version 1.0 was released in August 2018 by the Gradle team and there has been a lot of work going on ever since. It’s definitely ready for productive use, even if there are still a few issues. As an officially supported language for Gradle, Kotlin examples are also included in the documentation.
Doesn't that make you curious? So, the question is how to get started. Most build scripts of existing projects are still written in Groovy. Therefore, this article shows five simple steps to migrate them to the Gradle Kotlin DSL. It’s based on a live demo from one of my talks. The project on GitHub includes the live demo and provides links to the references/documentation and a more detailed introduction into the migration with complementing code.
dbaelz / AndroidHeadsVienna
Gradle Kotlin DSL: Migration Demo and Guide for an Android project
Furthermore, the official GitHub repository contains many samples.
gradle / kotlin-dsl-samples
Samples builds using the Gradle Kotlin DSL
Step 0: Get to Know the Project
Usually, an Android project with Gradle and Groovy consist of three Gradle build script files: In the root (top-level) directory the build.gradle
(common configurations) and the settings.gradle
. Another build.gradle
is located in the app module for the Android app specific configuration. The demo project has this exact structure, but even more complex setups can be easily migrated. It’s also possible to use Groovy and Kotlin build scripts in one project. However, this goes beyond the scope of this “five simple steps” article. The official documentation has some useful information about the interoperability.
My approach for the migration of the build scripts is quite simple: Change the Groovy syntax so it’s more like that of Kotlin, migrate the scripts to the Gradle Kotlin DSL, refresh them and check for errors. Afterwards, fix the errors until it's building again. But enough of this preface, let's get started with our simple 5 step migration.
Step 1: Minor Modernization
Projects generated by Android Studio still use the legacy apply()
function for applying plugins. We should get rid of it and use the plugins {}
block instead. The apply()
function still works with the Kotlin DSL, but only plugins {}
provides advanced features like type-safe accessors for extensions/configurations.
Step 2: Adapt Quotes
In Groovy single and double quotes are used for Strings. There are subtle differences between them, but they aren’t relevant for the migration. What’s relevant is that in Kotlin only double quotes are supported. So we just replace all single with double quotes. Search and replace to the rescue!
Step 3: Make it Kotlin-ish
Another difference between both languages is that Groovy has more cases of optionality than Kotlin. For example optional parentheses for method calls. Such calls aren’t valid syntax in Kotlin. So we have to change them as well. What at first seems like a straightforward approach raises further questions:
- Are these methods still provided by the Kotlin DSL as methods or instead as properties?
- Could we call them as property instead of methods (e.g.
versionCode
instead ofgetVersionCode()
)? - Are there Extension Functions provided by the Kotlin DSL?
To answer these questions we have to take a deeper look into the documentation and the source code of the Kotlin DSL and check every assignment and function call. Or we could just change them to the best of our knowledge (for example to method calls) and fix them after the migration. That's the approach I'd like to recommend, because it's done much faster.
Step 4: The (Real) Migration
Step number 4 is the fastest and easiest in this article. The Groovy Gradle scripts end with the .gradle
extension, the Kotlin DSL scripts with .gradle.kts
. So we just have to rename the three existing files and refresh the scripts.
Step 5: Fixing Errors
After renaming and refreshing the build scripts, the demo project won't build but show some errors. That’s something that (probably) happens to every project after the Kotlin DSL migration. In this step we’ll fix the errors and make sure it's a working project again. Of course, in a comprehensive project this step is the most complex and the hardest. However, there are common/typical fixes for an Android project and I will show you how to tackle them. They are explained in more detail in the demo project, and to follow up a look at the source code of this project is helpful.
Build Types and Configuration Actions
In Groovy configuration actions for the different build types could be added just by name and a closure that contains the configuration (e.g. release {}
). On runtime, the name is extracted, the configuration located and the action added to the project. In a statically-typed language such as Kotlin, this kind of magic doesn’t work. Instead, we have to locate the configuration with a provided function (getByName("release") {}
) and define the configuration with a lambda. On the upside, in contrast to a Groovy script, auto-completion and jumping to the source in lambda is supported.
Assignments, Method Calls and Properties
When we fix the configuration actions in the demo project and refresh the build script another error occurs. Gradle tells us, that the minifyEnabled
variable doesn't exist. Due to the auto-completion it's easy to find out that it must be replaced with a function call or the property syntax (e.g. isMinifyEnabled = false
). These changes to method calls or property syntax will happen in nearly every migration and can usually be fixed that easily.
There are also more elaborate cases, so the demo project shows how to migrate static functions, custom tasks and Gradle properties. To keep it short, they are only briefly touched here. But there are some simple tips: The easiest way to migrate existing Groovy functions is to rebuild the Groovy logic with variables and methods from the Java/Kotlin Standard Library. Furthermore, the Kotlin Gradle DSL provides some helpful extension functions. Migrating (custom) tasks is also not a problem, because the DSL provides helper methods to create and expand them. To get a Gradle property as variable it must be declared and the initialization delegated to the project object. This is different to Groovy, where it’s (magically) bound to the variable but very easy to implement.
Version Variables
Gradle Groovy scripts use the extra properties (ext
) in the root build scrip to define the versions of dependencies. These versions (e.g. kotlin_version
) are often used in the root and the app build script. The simplest way to migrate these variables would be to change the ext
properties to Kotlin variables using the val
keyword. Unfortunately, these variables would only be accessible inside the containing script. As a quick fix we could just copy the version information into the dependency declaration. Of course, this leads to more redundancy and is a step backwards compared to the Groovy script. The Bonus section below shows a great way to improve this.
Bonus
At this point, the migration of the Groovy build script to the Kotlin DSL is done. Everything should work as before and in addition the advantages of the Kotlin DSL such as type-inference (and therefore auto-completion) should ease the work with Gradle build scripts. So we could lie back and do other things. Or we could make further optimizations which increase the maintainability and reusability of the build scripts.
buildSrc Directory
In my life as a developer I’ve encountered many extensive build scripts containing tons of variables, methods and task definitions. Of course, in the production code of the app I would structure and modularize such code. Couldn't I do that in the build script, too?
Yes, I can. The buildSrc directory provides a way to encapsulated custom tasks, plugins and imperative logic outside of the build scripts. Code inside the directory is compiled and added to the classpath of all build scripts in the Gradle project. This feature isn’t something introduced with the Kotlin DSL, it’s been available in Gradle for some time and can be used with Groovy as well. As I mentioned above, after the migration the version information has been duplicated. This is a great use case for the buildSrc. We just create a Kotlin source file inside of buildSrc, define an object class and move the version information as variable inside the class. Now we can use this variable in our build scripts and remove the redundant definitions in the root and app scripts. Furthermore, additional logic could be moved into the buildSrc. For example, the build script in the demo project includes a function generateVersionCode()
with some logic to calculate the version code of the app. This logic could be moved so that the build script becomes more compact and readable. Depending on your current project, there might be several other code snippets that could be moved to buildSrc.
Summary
Migrating the build scripts of a typical Android app to the Gradle Kotlin DSL isn’t that hard. Of course, for more complex build scripts with many plugins, tasks and logic the effort can increase. However, in most cases it’s achievable by following the described procedure and within an acceptable amount of time. On the other hand there are some advantages like auto-completion and better understandable scripts (from an app developer's perspective). Therefore, I would recommend everyone to try out the Gradle Kotlin DSL in their project.
Top comments (0)