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" }
Job Offers
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.
This article was previously published on proandroiddev.com