If you are an Android developer with experience, you have gone through the different phases of using Gradle to manage dependencies in Android.
First, it was done through Groovy. Easy to learn, not crazy different to Kotlin or Java, but it could be better.
Then, we jumped to the Kotlin Gradle DSL, and magically we could use the same language for our configuration files and for our base code.
Now, we have a new tool to manage dependencies, called version catalog, and the great thing is that it can be used with Groovy and with the Kotlin DSL.
Let’s see how to use it in a project, what are we going to gain using this tool, and what can be some of its disadvantages.
How to add it to a project?
The most used technique nowadays in my experience is to use the Gradle Kotlin DSL to manage dependencies by creating a module, usually called buildSrc
. This module has different Kotlin objects for the names of the dependencies, the Gradle plugins and all the version numbers.
With the version catalog, we don’t need this module to have an organized set of dependencies.
The buildSrc module can be used for more things besides organizing your dependencies, so you may need to keep it anyway.
Now we just need a TOML file, named libs.versions.toml
, that is going to be placed in the folder called gradle
in the root of the project and to upgrade the Gradle version to at least 7.4 version, which can be done in the gradle-wrapper.properties
file.
Writing the dependencies
To manage our dependencies, we are going to need three clear sections in the TOML file, versions
, libraries
, and plugins
.
This thing is pretty straightforward, as you can see in the next code snippet.
[versions]
activityCompose = "1.5.1"
androidxCore = "1.9.0"
androidxEspresso = "3.4.0"
androidGradlePlugin = "7.3.1"
appCompat = "1.5.1"
compose = "1.2.0"
[libraries]
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspresso" }
app-compat = { module = "androidx.appcompat:appcompat", version.ref = "appCompat" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
# Some other ktor dependencies that I'm skipping in this example to make it simpler
[bundles]
ktor = ["ktor", "ktor-serialization", "ktor-core", "ktor-cio", "ktor-content-negotiation", "ktor-json", "ktor-kotlinx-json", "ktor-logging", "ktor-okhttp", "ktor-client-android"]
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
TOMLYou define your versions, your libraries associated to each version, and the same thing with plugins.
If you’ve read the snippet, you should have noticed the bundles
section. This thing is great for libraries like Ktor or Compose that usually have a lot of different dependencies you may need.
Thanks to it, when you want to use the dependency in the build.gradle.kts
file, you will need to just add the bundle you’ve created, instead of all the dependencies one by one in every module that you need it to be.
Using the dependencies
After a Gradle sync, we can use this new version catalog in the build.gradle.kts
file or the equivalent in Groovy. For these samples I’m going to use the Gradle Kotlin KTS as it’s what I’m more comfortable at the moment.
For the plugins, we have to do it like so.
@Suppress("DSL_SCOPE_VIOLATION") // Remove when fixed https://youtrack.jetbrains.com/issue/KTIJ-19369
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
KotlinThere are some things to talk about here.
We defined our plugin dependencies using hyphens, but now we are using dots! This way you can access them like it were an object, and it’s going to suggest the libraries while you are writing, so no need to memorize them.
There’s also a bug that is going to raise a warning in the plugins section, but it’s actually fine, already reported and if nothing weird happens, going to be fixed.
To add a dependency to the module, we would do it like this.
dependencies {
implementation(libs.activity.compose)
api(libs.compose.ui)
}
It couldn’t be easier.
If you have a lot of dependencies in the same file, you can use the bundles we talked about before, or use the with keyword to avoid writing libs a thousand times.
Downsides
If you are thinking about migrating your existing project, this is going to be a boring process that’s going to take you some hours.
It’s kind of repetitive writing every dependency or copy-pasting them, and for me and for many, this is the kind of task we want to avoid at all costs.
I’ve seen people using GitHub Copilot for this, and it did actually save them time, but at the end of the day, you are going to need to spend some hours with it, assuming everything goes as planned and being extra careful with the code Copilot suggests.
There is a plugin that in theory does this migration for you and keeps versions updated, but I haven’t tested it personally, so I can’t recommend it.
Another disadvantage could be that most of the current developers are educated on the traditional way of managing dependencies with Gradle. They are going to need to invest a bit of time understanding how the version catalog works, but you can share with them this article for that. 😉
Advantages
Bundles are great! This is what I liked the most. Adding all the Jetpack Compose, Firebase or Ktor dependencies with just one line of code is beautiful.
In theory, if you only use the buildSrc
module for dependency management, and you are able to remove it, compile times are going to be faster with the version catalog. I don’t believe this is going to be something that you can notice, but it’s something to think about.
As we said before, once you write libs
, dependencies are going to be suggested to you, so no need to memorize the exact name of a dependency again, and you can also check if the dependency is already setup in the project without opening the TOML file.
Conclusions
Now that we know how to implement the version catalog, what are some of its downsides and advantages, we need to ask ourselves the question.
Is it worth it?
In my opinion, it depends. For new projects, absolutely yes! Migrating to the version catalog from a fresh project is going to take you only a few minutes (specially if you’ve done it before), and you are going to have a clean solution to manage your dependencies with direct support from Gradle.
For new projects, things are different. You have to take into account the time that is going to take the migration and see if members of your team would like to adopt this new approach.
And now to finish, here you have a project in GitHub made by Google themselves that uses the versions catalog if you need it.
Featured image by DALL·E 2
If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.