Skip to main content

Command Palette

Search for a command to run...

Efficient Dependency Management in Android

Unleashing the Power of Version Catalog

Updated
3 min read
Efficient Dependency Management in Android
E

Hello there, I'm Eric Wafula, an Android Developer passionate about pushing mobile technology's boundaries. Over the past 2 years, I have honed my skills in Java, Kotlin, and the Android SDK, using them to build apps that redefine how users interact with digital platforms.

My fascination with technology, coupled with an inherent problem-solving knack, led me on a self-guided journey into Android development. In this path, I've created a range of projects, from dynamic e-commerce apps to innovative productivity tools. Each application I've built has taught me more about the complexities of coding and the delicate intricacies of user-friendly design.

I started this blog to document my experiences and share my insights from my continuous exploration of Android development. Whether you're a self-taught developer, a tech enthusiast, or just curious about how apps are made, there's something here for you.

When I'm not knee-deep in code, you'll find me contributing to open-source projects, experimenting with the latest tech trends, or connecting with other developers in various online communities. I'm a firm believer in lifelong learning and the power of sharing knowledge, which is the main drive behind this blog.

Welcome to my corner of the internet. Let's embark on this exciting journey in the realm of Android development together!

Every Android developer has worked on a monolith and managing dependencies for such a project is quite straightforward because you only get to deal with a single module. However, things start to get a bit tricky when you want to add another module to your project because of repeating the whole process of redeclaring the same dependencies in the other module.

This goes against the principle of "Don't Repeat Yourself(DRY)" which states that "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."

When we think of dependency management regarding this principle, we can apply it in several ways.

  1. Avoid having multiple versions of the same library in different parts of your project.

  2. Have a centralized place that contains all the dependency information.

  3. Having a system that automatically updates the application dependencies.

I won't delve into automatic updates of dependency versions because it's beyond the scope of this article but I will create a separate article that talks about that in detail.

So what is Version Catalog?

Well, It's simply a Gradle feature that was introduced in Gradle 7.0 that allows us to have all our dependencies in a centralized place.

Before the Version Catalog, most developers used to create a buildSrc directory which Gradle treats as an included build. Feel free to look at it at your convenience.

Let's go through the steps involved in creating a version catalog:

  • In a new/existing Android project, switch to the project view.

  • Look for the gradle folder, right-click, select New > File

  • Name the file: libs.versions.toml

You should be having something like this at the end

The Version Catalog structures the dependency details in sections as shown below

This is the current state of our dependencies after the initial project creation

dependencies {

    implementation("androidx.core:core-ktx:1.10.1")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation(platform("androidx.compose:compose-bom:2023.03.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

Let's start moving the androidx-core-ktx dependency to the version catalog.

[versions]
androidx-core-ktx = "1.10.1"

[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" }

This is how the dependency will look afterward

implementation(libs.androidx.core.ktx)

I've gone ahead and moved the rest of the dependencies to the version catalog.

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.ui.graphics)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.compose.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.compose.ui.test.junit4)
    debugImplementation(libs.androidx.compose.ui.tooling)
    debugImplementation(libs.androidx.compose.ui.test.manifest)
}

You can notice that we have a few common dependencies. We can add them to the bundles section in the Version Catalog as shown below.

[bundles]
implementation = ["androidx-core-ktx", "androidx-lifecycle-runtime-ktx", "androidx-lifecycle-viewmodel-compose", "androidx-activity-compose", "androidx-compose-ui", "androidx-compose-ui-graphics", "androidx-compose-ui-tooling-preview", "androidx-compose-material3"]
testImplementation = ["junit"]
androidTestImplementation = ["androidx-junit", "androidx-espresso-core", "androidx-compose-ui-test-junit4"]
debugImplementation = ["androidx-compose-ui-tooling", "androidx-compose-ui-test-manifest"]

Now our dependencies block looks a bit cleaner

dependencies {

    implementation(libs.bundles.implementation)
    implementation(platform(libs.androidx.compose.bom))
    testImplementation(libs.junit)
    androidTestImplementation(libs.bundles.androidTestImplementation)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    debugImplementation(libs.bundles.debugImplementation)
}

How about plugins? well here is how they will be added to the version catalog

[versions]
android-application = "8.1.0"
kotlin-android = "1.8.10"

[plugins]
android-application = { id = "com.android.application", version.ref = "android-application" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-android" }

The project-level Gradle file

// before
plugins {
    id("com.android.application") version "8.1.0" apply false
    id("org.jetbrains.kotlin.android") version "1.8.10" apply false
}

// after
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
}

The app-level gradle file

// before
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

// after
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

Conclusion

We have seen how working with the version catalog makes it easy for us to manage the libraries we add to our project. This will be even more beneficial as you start modularizing your project because you will notice how much boilerplate will be reduced. You will get even more benefits when you start considering using the Gradle Convention Plugins to share your build logic between submodules.

Happy Hacking!