If you are an Android developer looking to speed up your builds, you need to rethink your approach to dependency management. Before you massively redesign your architecture or divide your app into multiple modules, try version catalogs first. You will notice certain improvements in development, and the team will save some time. In the meantime, you can make big changes to your project to improve your build times.

The version catalog provides the ability to keep and manage your dependencies in one place. Since Gradle 7.0 it is possible to use it in our projects. A file called libs.versions.toml, which should be located in the /gradle folder in a project. With this file we can define our dependencies for the project.

At the moment android studio only supports version catalogs in TOML format. But it is easy to catch up. Basically this toml file consists of 4 sections.

[versions]

[libraries]

[plugins]

[bundles]
#libs.versions.toml

The [versions] section is for specifying versions that can be referenced by dependencies. You can also define your build constants like targetSdk, versionCode etc. You can also see warnings about available new versions.

The [libraries] part is for declaring aliases for dependencies. As shown below, version.ref is for getting a reference of the version from the [versions] part. Also, there are 2 different names for the reference to dependencies: group and module. module is the complete definition of the library, without separating two parts. And group can be used for some libraries which have the same root, and the name part distinguishes them from each other.

[versions]
retrofit2 = "2.9.0"
coroutinesCore = "1.7.3"

[libraries]
retrofit-core = { group = "com.squareup.retrofit2", name ="retrofit", version.ref = "retrofit2" }
retrofit-gson = { group = "com.squareup.retrofit2", name="converter-gson", version.ref = "retrofit2" }
coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutinesCore" }
#libs.versions.toml
  implementation ("com.squareup.retrofit2:retrofit:2.9.0")
  implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
  implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
//build.gradle.kts

Example usage of catalogs;

dependencies {
    implementation(libs.retrofit.core)
    implementation(libs.retrofit-gson)
    implementation(libs.coroutines)
}//build.gradle.kts

the [plugins] part is for declaring plugins.

[plugins]
com-android-application = { id = "com.android.application", version.ref = "agp" }
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" }
org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org-jetbrains-kotlin-jvm" }
com-android-library = { id = "com.android.library", version.ref = "agp" }
#libs.versions.toml

Plugins can be referred as:

plugins {
    alias(libs.plugins.com.android.application) apply false
    alias(libs.plugins.org.jetbrains.kotlin.android) apply false
    alias(libs.plugins.org.jetbrains.kotlin.jvm) apply false
    alias(libs.plugins.com.android.library) apply false
}//build.gradle.kts

The [bundles] part is for grouping dependencies that have similar contexts. The most known example is retrofit library. We need moshi or gson for retrofit for most of times. So we can create bundle for that and we can define once for all places can be used.

[libraries]
retrofit-core = { group = "com.squareup.retrofit2", name ="retrofit", version.ref = "retrofit2" }
retrofit-gson = { group = "com.squareup.retrofit2", name="converter-gson", version.ref = "retrofit2" }

[bundles]
retrofit = ["retrofit-core", "retrofit-gson"]
#libs.versions.toml

usage of bundles;

dependencies {
  implementation(libs.bundles.retrofit)
}//build.gradle.kts

FUN FACT: If your application consists of multiple modules, you can use bundles to group the dependencies of each module and then manage them all on the same line. You can see an example of how to use it below:

[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }

[bundles]
feature-login = ["core-ktx","appcompat","material","constraintlayout"]
feature-login-test = ["junit"]
feature-login-android-test = ["androidx-test-ext-junit", "espresso-core"]
#libs.versions.toml

these bundles can be referenced from build.gradle file in login module as:

dependencies {
    implementation(libs.bundles.feature.login)
    testImplementation(libs.bundles.feature.login.test)
    androidTestImplementation(libs.bundles.feature.login.android.test)
} //build.gradle.kts 

Pros & Cons

  • Version Catalog is better than a buildSrc module for our dependencies. This is because any change within buildSrc would invalidate the entire cache and require the entire project to be recompiled. This causes longer build times. But in the version catalog, changing a dependency version would only effect the parts that use that dependency. This may not be a problem for small projects but its impact would be huge when dealing with projects with multiple modules
  • You do not need to create another module for dependencies like buildSrc. It is convenient to use in multi-module applications. All variables in version catalog will be accessible for each gradle file in the application.
  • You do not have to give much attention and time as ext blocks.
  • Flexible and easy to use. You can also extend the use of dependencies via bundles.
  • It is easier to maintain than other methods. It gives you hints about version updates of libraries.