Version Catalogs Migration: Scalable Dependencies Management

Version Catalogs Migration: Scalable Dependencies Management
Photo by Pickawood / Unsplash

Introductions

This article demonstrates how to migrate our dependencies management from deps.gradle to version catalog toml, a new feature in Gradle 7.4. We decided to implement this feature after upgrading our project to Gradle 8.1.2.

Version catalogs enable us to add and maintain dependencies and plugins in a scalable way, by creating a central list of dependencies that various modules can reference in a type-safe way with Android Studio assistance.

This can make our build scripts more readable, maintainable, and consistent. Our previous approach was to use a deps.gradle file with this format:

def versions = [
    kotlin: '1.8.21',
    dagger: '2.43.2',
    retrofit: '2.9.0',
    okhttp: '4.9.3',
    okio: '3.0.0',
    -------
]

deps.build = [
    compileSdkVersion: 33,
    -------
]

-----

Prerequisites

To follow this article, we need to have the following prerequisites:

  • A Gradle project that uses dependencies.gradle.kts to manage dependencies and plugins
  • Gradle 7.4 or newer installed on our machine
  • Android Studio Arctic Fox 2020.3.1 or newer

Steps

The migration process consists of the following steps:

Create Version Catalog File

We start by creating a version catalog file in our root project’s gradle folder, named libs.versions.toml. Gradle looks for the catalog in this file by default, so we recommend using this name. In this file, we can define three sections: [versions], [libraries], and [plugins].

Define versions, libraries, and plugins

In the [versions] section, we can define variables that hold the versions of our dependencies and plugins. We can use these variables in the subsequent sections to avoid hardcoding the versions. For example:

[versions]
sdk-min = "23"
sdk-target = "34"
sdk-compile = "34"
jvm-target = "17"
cmake = "3.18.1"
agp = "8.1.2"

In the [libraries] section, we can define our dependencies using the group, name, and version attributes. We can also use the alias attribute to give a custom name to our dependency, which will be used to generate the type-safe accessor. For example:

[libraries]
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
dagger-android = { module = "com.google.dagger:dagger-android", version.ref = "dagger" }
dagger-android-support = { module = "com.google.dagger:dagger-android-support", version.ref = "dagger" }
dagger-android-proc = { module = "com.google.dagger:dagger-android-processor", version.ref = "dagger" }
hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }

One of useful features versions catalog is bundles, we can also defined bundled dependencies in [bundles] sections. For example:

[bundles]
compose-app = [
    "accompanist-drawable-painter",
    "accompanist-flow-layout",
    "accompanist-permissions",
    "compose-activity",
    "compose-animation",
    "compose-coil",
    "compose-constraint",
    "compose-lifecycle",
    "compose-material",
    "compose-navigation",
    "compose-runtime-rx",
    "compose-ui",
    "compose-ui-tooling",
    "compose-viewmodel",
]

In the [plugins] section, we can define our plugins using the id and version attributes. We can also use the alias attribute to give a custom name to our plugin, which will be used to generate the type-safe accessor. For example:

[plugins]
fladle = { id = "com.osacky.fladle", version = "0.17.4" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
sonar = { id = "org.sonarqube", version = "3.3" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

Define Version Catalog on `settings.gradle.kts`

We can define multiple version catalog that we used in our project like this:


dependencyResolutionManagement {
    ------------------
    versionCatalogs {
        create("dep") {
            from(files("gradle/dep.versions.toml"))
        }
        --- We can define other type of version catalogs below
    }
}

Replace the dependencies and plugins in the build files

After defining the version catalog file, we can sync our project and replace the dependencies and plugins in our build files with their catalog names. For example, instead of writing:

buildscript {

    dependencies {
        classpath dep.plugin.firebase.perf
        -------------
    }
}

plugins {
    alias(dep.plugins.fladle) apply false
    alias(dep.plugins.hilt) apply false
    ----------------------
}

dependencies {
    coreLibraryDesugaring dep.desugar.jdk
    implementation dep.kmk.vidikit
    testImplementation dep.bundles.test.androidx
    testImplementation dep.robolectric
    testImplementation dep.turbine
    testImplementation dep.test.coroutine
    ----------------
}

We can use the autocomplete feature of Android Studio to find the available catalog names.

Benefits

By migrating to version catalogs, we can enjoy the following benefits:

  • We can have a single source of truth for our dependencies and plugins, which can improve the consistency and maintainability of our project considering that we also have separate repository in the form of library such as our UI Kit, player, etc.
  • Because we are using single source of truth for our dependencies, we can reduces error causing by compatibility issue between libraries version from main project and libraries.
  • We can avoid duplication and typos in our dependency declarations, which can reduce the risk of errors and conflicts.
  • We can leverage the type-safe accessors generated by Gradle, which can improve the readability and autocomplete assistance of our build scripts.
  • We can easily update the versions of our dependencies and plugins by changing the variables in the version catalog file, which can save time and effort.

Conclusion

In this article, we learned how to migrate existing dependencies management from dep.gradle into version catalog toml, a new feature introduced in Gradle 7.4. We saw how to create a version catalog file, define versions, libraries, and plugins, and replace the dependencies and plugins in our build files with their catalog names. We also discussed the benefits of using version catalogs, such as improved scalability, readability, and consistency of our project. We hope this article was helpful and informative, and we encourage you to try out version catalogs in your own projects. Next we will describe how we deploy several version catalog and reuse it on multiple repositories.