Different ways to handle Android Gradle dependencies

As an Android developer, you use Gradle for adding dependencies, configurations, tasks, etc. related to your project. It is simple to update a basic application. When the application size increases, developers should also consider properly maintaining the versions, libraries, plugins, and bundles that are the prime source for apps.
As a common theory in coding, we use the function to reuse a piece of code in multiple sections. If we want to change any specific section, we only need to change the logic in the function. Likewise, we can use the version catalog, buildSrc, and ext as a single source that will help us maintain Gradle dependencies.
Sample use case
We can look at a sample use case for a multi-model project. For example, look at the following build: gradle files for the project named Sample, which has two modules, one app module, and a second sub module.
build.gradle(:app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.example.sample"
minSdk 21
targetSdk 31
versionCode 1
versionName "0.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
}
build.gradle(:sub)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 29
defaultConfig {
applicationId "com.example.sample"
minSdk 21
targetSdk 29
versionCode 1
versionName "0.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.9'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.6.0'
}
Problem Statement
So in the above use case, there are two build.gradle files: one for the app module and the other for the sub module. When we closely look into both build.gradle files, we can see that both build scripts use the same dependency with a different version and CompileSdk. If we build the project, it will result in conflict.
How to solve
1. Manually solving
You should check each module and upgrade each build. Grails files on your own. If not upgraded properly, there is a chance of conflict between build modules. It feels so appealing to solve for small-sized projects, but for larger ones, it becomes a headache for developers to solve each and every build script.
2. ext

With this approach, the build script should include information about the project’s external components, such as versions, dependencies, plugins, and bundles, in the ext block.
These components can be configured in two ways: by specifying the ext block in root build.gradle or by creating a new Gradle file and referencing it in build.gradle.
- build.gradle(root level) or create a separate version.gradle / version.gradle.kts in the project and write the following script
ext {
compileSdk = 31
versions = [
core-ktx= "1.7.9",
appcompat = "1.6.1",
material = "1.6.0"
]
dependency = [
coreLib:"androidx.core:core-ktx:${versions.core-ktx}",
appcompatLib:"androidx.appcompat:appcompat:${versions.appcompat}",
materialLib:"com.google.android.material:material:${versions.material}"
]
}
then call it in build.gradle
<!-------------Specify version.gradle in here--------------!>
apply from: '$rootDir/version.gradle'
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
<!--------Use the compileSdk from ext like this-------------!>
compileSdk ext.compileSdk
defaultConfig {
applicationId "com.example.sample"
<!---Also you can specify the details in here like minsdk, target, etc.. and call by ext.name-----!>
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
<!--------specify dependencies from ext like this as dependency.name--------!>
implementation dependency.coreLib
implementation dependency.appcompatLib
implementation dependency.materialLib
}
3. builSrc
Another approach to solving the problem is by using buidSrc. We can combine it with Kotlin DSL for better dependency. For this, we can create a buildSrc directory with additional files Configuration.kt, which contains configuration-related details, and Dependency.kt, which contains dependency build scripts. With the enabling of DSL mode in build.gradle.kts of buildSrc.

- Create a new buildSrc directory in the project then create build.gradle.kts in the buildSrc directory
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
- Create directory src/main/kotlin and create files like
Configuration.kt
object Configuration {
const val compileSdk = 31
const val targetSdk = 31
const val minSdk = 21
const val majorVersion = 0
const val minorVersion = 1
const val patchVersion = 0
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
const val versionCode = 1
const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT"
const val artifactGroup = "com.example.sample"
}
Dependencies.kt
object Versions {
internal const val APPCOMPAT = "1.6.1"
internal const val MATERIAL = "1.6.0"
internal const val CORE = "1.7.9"
}
object Dependencies {
const val appcompat = "androidx.appcompat:appcompat:${Versions.APPCOMPAT}"
const val material = "com.google.android.material:material:${Versions.MATERIAL}"
const val core= "androidx.core:core-ktx:${Versions.CORE}"
}
Then in build.gradle.kts specify as following
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
<!----------------- Use Configuration section in here---------------!>
android {
compileSdk = Configuration.compileSdk
defaultConfig {
minSdk = Configuration.minSdk
targetSdk = Configuration.compileSdk
versionCode = Configuration.versionCode
versionName = Configuration.versionName
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled = false
proguardFiles (
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
<!----------------- Use Dependencies section in here---------------!>
dependencies {
implementation(Dependencies.core)
implementation(Dependencies.appcompat)
implementation(Dependencies.material)
}
4. Version Catalog
The last method I’m going to mention is using the Version Catalog. A version catalog contains a list of dependencies, represented as coordinates. We have two options for implementing it. The first is to specify it in the settings.gradle file, and another way is to create a libs.versions.toml file in the gradle section of the project.
- settings.gradle add the following
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
versionCatalogs {
libs {
version('appcompat','1.6.1')
version('core','1.7.9')
version('material','1.6.0')
library('core-ktx','androidx.core','core-ktx').versionRef('core')
library('appcompat','androidx.appcompat','appcompat').versionRef('appcompat')
library('material','com.google.android.material','material').versionRef('com.google.android.material')
}
2. Using TOML file format

The TOML mainly comprises four sections.
- versions : This section specifies details related to the versions.
- libraries : The library to be used in the project is specified here.
- bundle : The section covers bundles to be used in projects.
- plugins : Used to define plugins for the project.
Create a new TOML extension file in Gradle and name it libs.version.toml and add the following code
[versions]
core = "1.7.9"
appcompat = "1.6.1"
material = "1.6.1"
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
[libraries]
material = { module = "com.google.android.material:material", version.ref = "material" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
Use in build.gradle as follows
<!---------------use version catalog as follows--------------------!>
plugins {
id(libs.plugins.android.application.get().pluginId)
id(libs.plugins.kotlin.android.get().pluginId)
}
android {
compileSdk 29
defaultConfig {
applicationId "com.example.sample"
minSdk 21
targetSdk 29
versionCode 1
versionName "0.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
<!--------------------use dependencies as follows------------------!>
implementation(libs.material)
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
}
Conclusion
That’s a wrap for the article. In this article, we can see different ways to handle dependencies in Gradle. You can also incorporate it into your next project. If you want an example, you can use the following project, Pico, where I implemented a combination of various techniques specified above. If you’d like to add any further techniques, you can mention them in the comments.
If you like this piece of information, please show your support by sharing this article.