Blog Infos
Author
Published
Topics
,
Author
Published
Posted by: Satya Pavan Kantamani
Introduction

Since the start of using Android Studio, Gradle has become a central place for managing many things like dependencies, config data, flavours, etc. And maintaining the dependencies across multi-module projects has become a challenge over time. Let’s see in detail how the evolution of dependencies management across multi-module projects happened and the problems faced. Please check out my previous post on Kotlin DSL for a better understanding

The core part of this post is how we can achieve better dependencies management using buildSrc and Kotlin DSL

Problem

If you want to skip the evolution process move to the usage of buidSrc with Kotlin DSL section else continue reading.

Let’s take a multi-module app to understand the problem. Manually declaring versions along with dependencies across multi modules is our first approach. That would look something like below

At first module build.gradle file we declare dependencies

implementation "androidx.appcompat:appcompat:1.0.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41"
xxxxxx

In the second module build.gradle fiile we declare same dependencies

implementation "androidx.appcompat:appcompat:1.0.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41"
xxxxxxxx

As we are defining dependency versions at each build.gradle file this has created problems like conflicting versions if not upgraded correctly. It’s very hard to manage the version upgrades of dependencies and config data. Whenever there is a change in dependency if we have more modules there was a lot of manual process of going to each and every module and checking whether the dependency is exiting or not if found updating and then moving to next.

Configure project-wide properties

Then we moved to set project-wide properties using Gradle Extra properties. For this approach, we used to define the config data and dependency versions at the project or root-level build.gradle file

ext { compileSdkVersion = 28  versions = [
   supportLibrary = "1.0.0",
   kotlinVersion = "1.3.41"
  ]deps = [
       appcompatLib: "androidx.appcompat:appcompat:${versions.supportLibrary}",
  
  kotlinStdLib:                            "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlinVersion}"
  ]
}

Across modules simply use

compileSdkVersion ext.compileSdkVersionimplementation deps.appcompatLib
implementation deps.kotlinStdLib

This has solved the issue of upgrading versions of dependencies keeping them in one central place. However, there is no navigation support to check the versions or auto-suggestions. So let’s move to the core part of using buildSrc with Kotlin DSL

What is buildSrc?

buildSrc is a directory at the project root level which contains build info. We can use this directory to enable kotlin-dsl and write logic related to custom configuration and share them across the project. It was one of the most used approaches in recent days because of its testability,

The directory buildSrc is treated as an included build. Upon discovery of the directory, Gradle automatically compiles and tests this code and puts it in the classpath of your build script. For multi-project builds there can be only one buildSrc directory, which has to sit in the root project directory. buildSrcshould be preferred over script plugins as it is easier to maintain, refactor and test the code — Gradle Docs

Let’s see how to create a buildSrc directory

Right Click on Root Project → New → Directory → buildSrc

Enable Kotlin DSL in buildSrc

As Kotlin DSL is adopted from the parent Kotlin language most of its syntax would be similar. For this in buildSrc first, create an empty file build.gradle.kts and then enable the option of ‘kotlin-dsl’ in buildSrc

import org.gradle.kotlin.dsl.`kotlin-dsl`
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}

Click on sync now to finish the set-up. Once it’s done now we can create our Kotlin DSL scripts for custom build logic. Here let’s do configuration build data and dependency management set-up.

Step 1 :

Create a srcdirectory structure in buildSrc directoryv

Job Offers

Job Offers


    Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Mobile Engineer

    OLX Group
    Remote, Portugal, Spain, Romania, Poland
    • Full Time
    apply now

    Senior Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

Jobs

Step 2

In this directory, we can create our Kotlin files. Let’s create an object class called Versions.kt where we define all the versions related to plugins, dependencies, etc

object Versions {
const val gradlePlugin = "4.2.1"
const val kotlin = "1.5.0"
const val timber = "4.7.1"
const val appCompat = "1.3.0"
const val material = "1.3.0"
const val constraintLayout = "1.1.3"
const val jUnit = "4.12"
}
view raw Versions.kt hosted with ❤ by GitHub
Step 3

Let’s create a new file Dependencies.kt to define all our plugins, dependencies, etc

The file will be looking as below

/**
* To define plugins
*/
object BuildPlugins {
val android by lazy { "com.android.tools.build:gradle:${Versions.gradlePlugin}" }
val kotlin by lazy { "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" }
}
/**
* To define dependencies
*/
object Deps {
val appCompat by lazy { "androidx.appcompat:appcompat:${Versions.appCompat}" }
val timber by lazy { "com.jakewharton.timber:timber:${Versions.timber}" }
val kotlin by lazy { "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" }
val materialDesign by lazy { "com.google.android.material:material:${Versions.material}" }
val constraintLayout by lazy { "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout}" }
val junit by lazy { "junit:junit:${Versions.jUnit}" }
}
view raw Dependencies.kt hosted with ❤ by GitHub

While creating a file we can have auto-suggestions poping from Versions.kt as shown below. So we can easily navigate between file to check the specific values

Step 4

We can also create a ConfigData.kt for any build configuration related info

object ConfigData {
    const val compileSdkVersion = 30
    const val buildToolsVersion = "30.0.3"
    const val minSdkVersion = 21
    const val targetSdkVersion = 30
    const val versionCode = 1
    const val versionName = "1.0"
}
Step 5

Finally using the data defined in the above classes in the module-level build.gradle.kts file

plugins {
id("com.android.application")
kotlin("android")
}
android {
compileSdkVersion(ConfigData.compileSdkVersion)
buildToolsVersion(ConfigData.buildToolsVersion)
defaultConfig {
applicationId = "com.sample.dsl"
minSdkVersion(ConfigData.minSdkVersion)
targetSdkVersion(ConfigData.targetSdkVersion)
versionCode = ConfigData.versionCode
versionName = ConfigData.versionName
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
dependencies {
implementation(Deps.kotlin)
implementation(Deps.appCompat)
implementation(Deps.materialDesign)
implementation(Deps.timber)
implementation(Deps.constraintLayout)
testImplementation(Deps.junit)
}

The final directory structure would be something like below

buildSrc
├── src
│ └── main
│ ─└── kotlin
│ ──└── Dependencies.kt
├── build.gradle.kts

Please check out the GitHub repo android_sample_kotlin_dsl for the sample codebase.

Note: Though we can do this declaration of versions, dependencies, etc in a single class it would be better to have segregation of classes so that the code will be clean

Summary

buildSrc + Kotlin DSL is the best option for dependency management. As it’s a class-level declaration it can be easily tested. The auto-suggestion support and code navigation would help in saving time. Maintain separate classes for each purpose. This approach could be easy for better reusability and easy maintenance.

Don’t forget to clap if you found this article helpful. Thank you for reading…

References

 

Continue Reading Android Stuff

Understand How View Renders in Android

The Life Cycle of a View in Android

Kotlin Series

How to Implement In-App Purchases in Your Android App

Many More

Tags: Android, AndroidDev, Programming, Kotlin

 

View original article at:


Originally published: June 22, 2021

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
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