I’m revisiting some helpful content for starting a new Android project. One of the pain points is how to handle a ton of dependency declarations for multi-module projects. Here is where version catalogs come to our rescue.
Declaring dependencies
Before discussing the version catalogs, we will see how dependencies are being declared in an Android project. To declare a dependency in an Android project, you would add something like the following code in your build.gradle.kts
file:
plugins {
//...
}
android {
//...
}
dependencies {
// Here is where the dependencies are declared
implementation("androidx.core:core-ktx:1.12.0")
// ...other dependecies
}
Coordinates
One important concept to have in mind is the concept of coordinates. Each coordinate is composed by “[group]:[artifact name]:[version]”. For example, in the example above the coordinate of the core-ktx
dependency is [androidx.core]:[core-ktx]:[1.12.0]
, so the same dependency can be decomposed in a declaration like this.
implementation(group = "androidx.core", name = "core-ktx", version = "1.12.0")
Keep in mind this concept; we will return to it later.
A common issue with multi-module projects
In case you have more than one module in your project, each dependency needs to be declared on every module that requires the specific dependency. The problem arises with this approach because of having repetitive declarations; every time you want to update a dependency version, you have to make the change in every gradle file where the dependency is declared. This makes dependency handling redundant and messy.
Here is where Version catalogs come to our rescue.
Version catalogs
Version catalogs is a feature of Gradle, so you can use it if you use Gradle as your build system in your Android project. This allows you to declare and organize all your dependencies in a single file, which acts as a catalog where each dependency is associated to a single alias, making this catalogs acts as a source of truth and it can be referenced in every gradle file.
To enable this option you need to follow these steps:
- Create a new file into the
gradle
folder at the project level with the namelibs.versions.toml
. - Declare the dependencies into that file.
- Reference these declarations in every gradle file of each module.
If you don't know where the gradle folder is, you can typically find it at the root project level; you can use the Project view to look at it.
At this point, you can ask… how these declarations must be made into that file?
Basic usage
Now, let's see the structure of the libs.versions.toml
file.
[versions]
core-ktx = "1.12.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
In the most basic format you have to declare two sections [versions]
and [libraries]
, the first one is used to declare versions for each dependency, the second is for the dependencies itself.
For this example, we took the same dependency that it has been declared in the first example of this article, in the gradle file. In order to make the core-ktx
dependency available into the libs catalog, we associate the alias androidx-core-ktx
to the GAV (group, artifact, version) coordinates.
The main benefit is these declarations are visible by all the modules in your project. So you can reference these declarations from your gradle file.
plugins {
//...
}
android {
//...
}
dependencies {
// Here is where the dependencies are declared
implementation(libs.androidx.core.ktx)
// ...other dependecies
}
Note: Every time you added a new declaration in the TOML file you have to sync the project to make it available for the modules.
Also, using Version catalogs let you to reference the same version for multiple different libraries, for example:
[versions]
android-paging = "3.2.1"
[libraries]
android-paging-common = { module = "androidx.paging:paging-common-ktx", version.ref = "android-paging" }
android-paging-runtime = { module = "androidx.paging:paging-runtime-ktx", version.ref = "android-paging" }
android-paging-rxjava2 = { module = "androidx.paging:paging-rxjava2-ktx", version.ref = "android-paging" }
Here, we simplify the coordinates using the module to group the [group]:[artifact]
pair and use the same version reference for all the android-paging dependencies.
Going a bit further
Not only you can declare the regular dependencies in that TOML file, you can declare your plugins and even create bundles for grouping dependencies.
How to declare plugins
To declare a plugin you only need to add the [plugins]
section.
[versions]
//Here the versions are declared
android-gradle-plugin = "8.2.0"
[libraries]
//Here the libraries are declared
[plugins]
android-application = {id = "com.android.application", version.ref = "android-gradle-plugin"}
So, after syncing the project, we can change the references from this
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.2.0" apply false
//... more plugins are placed here
}
To this
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
//... more plugins are placed here
}
How to create your own bundle
To create a bundle, you have to add a [bundles]
section into your TOML file. For example, here I’m adding the Room bundle.
[versions]
room = "2.6.1"
ksp = "1.9.0-1.0.13"
[libraries]
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
[plugins]
kotlin-symbol-processing = { id = "com.google.devtools.ksp", version.ref = "ksp" }
[bundles]
android-room-bundle = ["androidx-room-ktx", "androidx-room-runtime"]
Again, you can use the bundle like this after syncing your project.
plugins {
// More plugins are added here...
alias(libs.plugins.kotlin.symbol.processing)
}
android {
//...
}
dependencies {
// Some dependencies are declared here
implementation(libs.bundles.android.room.bundle)
ksp(libs.androidx.room.compiler)
}
Conclusion
It’s also worth mentioning that using a version catalog is likely the best way to establish a single source of truth for all your project dependencies. It's also worth mentioning that the official documentation has a section about this topic.
You can experiment with different strategies using bundles to organize the necessary dependencies for each project, avoiding the tedious process of checking each Gradle file to see what you’ve declared.
References
Migrate to version catalogs
https://developer.android.com/build/migrate-to-catalogs
Sharing dependency version between projects
Top comments (0)