Migrating from Groovy to Kotlin-DSL with Version Catalogs

Lucca Beurmann
5 min readApr 8, 2022
Banner containing Kotlin’s logo along Gradle logo symbolizing union

Manage your project’s dependencies, write scripts, and more with auto-complete and type safety.

Many Android developers who worked on multiple projects in the last few years are probably familiar with Groovy as the Gradle scripting language and build script extensions or buildSrc to manage dependencies and libraries versions. These solutions are considered solid, widely used in projects worldwide, and have a lot of content online to help developers with common issues and unexpected behaviors. But instead of going directly to the migration let’s make some considerations about the existing methods. If you aren’t interested or you are already familiar with buildscript ext and buildSrc feel free to skip to the First steps of the migration section.

Considerations on buildSrc method

The buildSrc solution provides type safety and an isolated Gradle module to manage external dependencies-related files, such as the dependencies themselves and their respective versions. Eventually, one or two helper methods are declared to make it easier for the development team to declare them in build.gradle files. So far it looks great right? But problems start to arise when changes in the buildSrc files are made. Editing files in the buildSrc module causes the gradle to be invalidated, and depending on the size of your project invalidating the current build-cache can take to very long build times.

Brief considerations on build script ext method

Buildscript ext is a globally available block of assignments that cat be accessed by any other build files inside the project.

Gist1: Example of main build file containing the ext block.

As you can see our libraries versions are all declared inside the ext block and can be globally accessed in our app module build.gradle file:

Gist2: Example using buildscript ext declared versions in a build file.

The problem with this approach is that sooner or later our project’s main build file will get bloated with dependency versions, and most importantly: the dependencies themselves are not centered in this file, making it difficult to keep track of which ones are used in the project.

Kotlin-DSL and Version Catalogs

The Kotlin-DSL is an alternative to Groovy as Gradle’s scripting language that makes it possible to use Kotlin as the language used in build files, guaranteeing type safety, code completion, and superior refactoring and documentation assistance.

Version Catalogs is a new feature in the Gradle system that aims to centralize the project dependencies (and their respective versions) so they can be used globally and type-safely among build files, through the generation of safe accessors for every version and dependency declared in the TOML file (we’ll talk more about this later). This way we can achieve centralized dependency management that doesn’t have the cache invalidation problem caused by the buildSrc.

Migration first steps

The following steps were implemented using Gradle Version 7.4.2 and AGP (Android Grade Plugin) 7.1.2. Keep in mind that using newer or older versions may cause differences in the implementation details.

Go to the gradle directory and create a file name libs.versions.toml . It’s important to use this exact name because it’s the default file that version catalogs work with, if you want to use a different nomenclature or even multiple version catalogs and you can find more information about this concern in the official documentation.

Image containing a visual representation of the steps needed to create the libs.versions.toml file.
Image1: project structure with versions TOML created

Declaring dependencies in the TOML file

The libs.versions.toml file will be responsible for containing information regarding our project’s dependencies, and to declare those dependencies we’ll use three blocks used by the Gradle plugin:

  1. [versions]: Block used to declare the versions of the libraries we’ll use.
  2. [libraries]: Block used to declare the dependencies themselves.
  3. [bundles]: Block used to declare groups of dependencies often used together (dependencies declared in a bundle must have the same configuration)

The libs.version.toml file looks like this after being filled with dependencies, versions, and bundles.

Gist4: Example of filled version catalog

Declaring Libraries

The library block is where we declare the dependencies used in the project along with their respective versions, notice that the libraries are declared in the module property and the version in the version.ref . So a library that was previously declared in build file using the following syntax:

Gist5: Example of dependency declared directly inside build file

Should be declared in a version catalog using the following syntax:

Gist6: Example of library declared in the version catalog

You’ve probably noticed that the libraries declared in my TOML file are all related to Jetpack Compose, when this kind of situation happens (multiple libraries of the same context) we can use dash syntax to create a “group” of libraries, so the accessors generated for these dependencies will differentiate each other with dot syntax. But if you don’t want to create groups for your dependencies you can simply declare them without the dashes and use them normally.

Gist7: Example of libraries declared using grouping

Declaring Versions

To declare new versions simply assign a name to a version code.

Gist8: Example of version declaration

Declaring Bundles

Declaring bundles is pretty straightforward, create an array of strings containing the names of pre-declared libraries (libraries declared in a bundle must have the same configuration, you can’t define a testImplementation and a regular implementation inside a single bundle).

Gist9: Example of bundle declaration

Using dependencies from the Version Catalog

Once your dependencies are set run Gradle Sync to prepare the accessor and now you should be able to retrieve values from your version catalog using libs keyword in both Groovy and DSL build files.

Gist10: Example of version catalog usage.
  • Bundles are accessed using libs.bundle.bundleName
  • Libraries are accessed using libs.libraryName
  • Libraries declared in groups are accessed using libs.groupName.libraryName . Note that libraries can be declared using nested groups, for example: compose-tooling-testing-preview . Keep in mind that every dash “-” is translated to a dot “.” when the accessors are generated, so in this case, you should use libs.compose.tooling.testing.preview .
  • Versions are accessed using libs.versions.yourVersion.get() .

Migrating to Kotlin-DSL

So far in this article we’re still using Groovy as our build files language, migrating to Kotlin-DSL is not that hard, all you have to do is edit your build files and append.kts extension:

Image2: Refactoring build file to use kotlin-dsl

If you try to Gradle Sync after migrating to DSL you’ll receive A LOT of errors in the build log, and presumably, the build will fail. This happens essentially because Kotlin-DSL uses actual Kotlin syntax in the build files, and as you can see, Groovy files don’t look like Kotlin at all. To migrate all you have to do is translate the Groovy code to Kotlin. The official documentation is a good headstart, but anyway I’ll leave an example of my build file after the migration.

Gist12: Build file migrated to Kotlin-DSL + Version Catalogs

Conclusions

Migrating to Kotlin-DSL + Versions Catalogs looks complicated when you hear about it without knowing the implementation details, but looking at the results after the migration is done we can see clearly: It’s not that complicated, looks good, and the most important (at least in my opinion): we are concentrating our project in a single language. Personally, I think that Version Catalogs is a great feature, and I'm excited to see new features being added by the Gradle’s Team to make our projects cleaner and safer over time.

--

--

Lucca Beurmann

Mobile Software Developer, technology enthusiast, video game lover and a cat person.