Connect with us

Articles

Strategies for managing Gradle dependencies

In the life of an Android Developer, library dependencies are like heart of the project. These library dependencies grow as the project grows because our modules, functionalities and requirements grows. So it becomes somewhat challenging to maintain them in the best way possible and have them available to all the modules in the project.

Strategy 1: So let’s start with the default way of managing the dependencies: adding the dependencies in project’s app.gradle

dependencies {  
    implementation 'androidx.fragment:fragment-ktx:1.3.6'
    implementation 'androidx.activity:activity-ktx:1.3.1'
}

Well this works fine as long as you’re having single module project and minimal nine to ten libraries. Beyond that this doesn’t seem to be an optimized way because once you enter into multi module arena you’ll find that

  • Different modules uses same libraries and then managing the version upgrade in them becomes hectic.
  • One may get lost in finding a library among the bundle of libraries.

So, Let’s try another approach.

Strategy 2: In this strategy we’ll try to segregate our dependencies in such a way that we can easily group them by category or any other way we find comfortable with. We’ll create a dependencies.gradle in our main project and inside it we’ll create extensions under the keyword ext as follows

ext {
    androidX = [
        compat        : 'androidx.appcompat:appcompat:1.3.1',
        recycler      : 'androidx.recyclerview:recyclerview:1.3.1',
        cardview      : 'androidx.cardview:cardview:1.3.0',
    ]
    network = [
        retrofit    : 'com.squareup.retrofit2:retrofit:2.9.0',
    ]
    di = [
        dagger     : 'com.google.dagger:dagger:2.40.5',
    ] 
    ....
}

As we see that now we’ve grouped our dependencies by type. Now we’ve androidX, dataConverters, network, di and so on. Now what if we’ve same version for multiple libraries? We can create constants as

def CompatVersion = '1.3.1'
def DaggerVersion = '2.40.5'
def NetworkVersion = '2.9.0'

Now we can reference these constants as

compat : 'androidx.appcompat:appcompat:$CompatVersion'

So how do we reference these extensions in our app.gradle? For that we first provide the reference to dependencies.gradle as follows

apply from: '../dependencies.gradle'

Now we can add these grouped dependencies using the extension from project object as follows

dependencies {
    implementation project.ext.androidX.values()
    implementation project.ext.ktx.values()
    implementation project.ext.dataConverters.values()
    implementation project.ext.network.values()
    implementation project.ext.di.values()
}

Yeah! Now this seems better and manageable. Although I didn’t find any major issue with this approach but it doesn’t show your dependencies in the project setting module.

Strategy 3: If you want a more precise way of having your dependencies then use Version Catalogs. As per Gradle, definition of version catalog is as follows

A version catalog is a list of dependencies, represented as dependency coordinates, that a user can pick from when declaring dependencies in a build script.

Benefits to version catalog are

  • enables auto completion in IDE
  • centralization of dependencies for all modules
  • grouping of dependencies

Result of Version catalog looks like

dependencies {

    implementation libs.core.ktx
    implementation libs.bundles.compose
    implementation libs.bundles.lifecyle
}

Isn’t cool? Let’s look at the setup for this way of management.

If you’re using Gradle version below 7.0 then you can enable this feature by adding the below line in settings.gradle

enableFeaturePreview("VERSION_CATALOGS")

You’ll have a resolution management section in settings.gradle as

dependencyResolutionManagement { }

Now, we can define our dependencies under the versionCatalogs -> libs

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
    versionCatalogs { 
         libs { 
         
         }
    }
}

We are provided with many options in version catalogs and we can use them as per our comfort in the project.

Define a version for dependency

version('compose','1.4.0')

Define the dependency

library('compose-activity','androidx.activity','activity-compose').versionRef('compose')

So our settings.gradle would look like something as

We can group dependencies using the bundle keyword. We can even define a plugin as

plugin('jmh', 'me.champeau.jmh').version('0.6.5')

There is one last important section we should discuss is when you want to share your dependencies across projects. We can do this using TOML(Tom’s Obvious Minimal Language) file format.

TOML format contains four sections namely [versions], [libraries], [bundles] and [plugins]. For example the above libs module can be defined in TOML as

[versions]
compose = "1.4.0"
composeui = "1.1.1"

[libraries]
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "composeui" }[bundles]
compose = ["composeui", "<other libraries ref>"]

[plugins]
jmh = { id = "me.champeau.jmh", version = "0.6.5" }

And we can import this in our gradle as

from(files("../libs.versions.toml"))

By default, the libs.versions.toml file will be an input to the libs catalog. It is possible to change the name of the default catalog, for example if you already have an extension with the same name

dependencyResolutionManagement {
    defaultLibrariesExtensionName.set('projectLibs')
}

And there are many more options which Version Catalogs provides. You can explore here if you want to know more about it.

You can choose whichever way suits your project but do give a try to version cataloging. Also if you are using some other way of managing dependencies then do share.

Hope this would be helpful.

Until next time!!

Cheers!

Full Article: Saurabh Pant @ ProAndroidDev

Advertisement

Trending