Blog Infos
Author
Published
Topics
Published
Topics

 

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"
}

 

OUR VIDEO RECOMMENDATION

, ,

Becoming and Staying a Productive Developer with Build Scans, Build Validation Scripts and Gradle

We will start by discussing some of the top Gradle issues that affect the productivity of Android developers. We will cover build cache misses and configuration cache issues as well as how to debug and…
Watch Video

Becoming and Staying a Productive Developer with Build Scans, Build Validation Scripts and Gradle

Etienne Studer & Nelson Osacky
SVP of Engineering & Lead Solutions Engineer
Gradle

Becoming and Staying a Productive Developer with Build Scans, Build Validation Scripts and Gradle

Etienne Studer & N ...
SVP of Engineering & ...
Gradle

Becoming and Staying a Productive Developer with Build Scans, Build Validation Scripts and Gradle

Etienne Studer & ...
SVP of Engineering & Lead ...
Gradle

Jobs

No results found.

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.

  1. 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

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Managing dependencies in a single module project is pretty simple, but when you start…
READ MORE
blog

Running Instrumented Tests in a Gradle task

During the latest Google I/O, a lot of great new technologies were shown. The…
READ MORE
blog
Many of us have faced Groovy difficulties and used to convert it to Kotlin…
READ MORE
blog
This is the accompanying blog post for my recent Droidcon Berlin 2023 talk “How…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu