Beginner’s Guide to Android Modularisation: Streamlining Shared Configurations/Settings with Kotlin DSL (.kts)

André Marques
6 min readNov 1, 2023

--

Introduction

There’s a fresh wave in the world of Android development: modularisation. As we witness the shift from monolithic applications to bite-sized, purpose-driven modules, the demands on developers like myself have evolved.

I recently found myself at the heart of this evolution when setting up a new Android project. Beyond the usual decisions about architecture and libraries, I was faced with a fundamental choice regarding build scripts: the classic Groovy scripts or the enticing ‘.kts’ files of Kotlin DSL? Being a dedicated Kotlin aficionado, the allure of Kotlin was too powerful to resist (chef’s kiss).

It’s worth noting that this piece won’t delve into the pros and cons of each choice, as that’s a discussion for another day. However, keep an eye out — I might tackle that comparison in a future article.

For more info: https://developer.android.com/build/migrate-to-kotlin-dsl

And just when you think you’ve got everything under control, the dreaded missing CompileSdkVersion pops up! Believe me, I searched high and low for a solution, but it was like hunting for a needle in a digital haystack. So, I decided to bridge this information gap myself.

Unified Vision: Streamlining Android Modules With Shared Settings

Ah, the euphoria of setting up a new Android project — like the whiff of a new book. But hold on, what’s this? An array of Android modules, each demanding its own set of configurations, nagging like a broken record about the missing SdkVersions?

Well, worry not! Let’s embark on a journey to effortlessly propagate shared settings across all your Android modules in one shot.

And for those skimmers who just swipe down to snatch code and dash away — tsk tsk, I see you! There’s a juicy code snippet waiting at the end, but perhaps, just perhaps, linger a while for the step-by-step explanation. Who knows, you might learn something new.

Just a heads up: that snippet is tailored for the project’s root build.gradle.

1. Enter the Subproject Arena

subprojects {

All aboard the `subprojects` train! This is where we tell Gradle, “Hey, listen up! I’ve got a set of rules, and I want them applied to all sub-projects (modules) under the main project.”

2. Unleash the AfterEvaluate Magic

afterEvaluate {

Every time a project is evaluated, we want to perform certain actions. It’s like checking the doors after everyone’s inside the house. In this case, is to ensure that you’re working with a fully configured project.

3. Smart Type Checks

(this as? ExtensionAware)?.extensions?.run {

Safety first! We’re confirming if the current project can be treated as having extensions before diving in.

4. Application-Specific Settings

findByType<BaseAppModuleExtension>()?.run {
defaultConfig {
applicationId = "com.appName"
versionCode = 1
versionName = "1.0"
}
}

Nailing down the basics for application modules. A default application ID and versions to get you started.

5. Ensuring Android Compatibility

findByType<BaseExtension>()?.run {
setCompileSdkVersion(34)

Shush! No more nagging about `CompileSdkVersion`.

6. Configurations for All Android Modules

defaultConfig {
minSdk = 34
targetSdk = 26
}

General configurations ensuring every module knows its minimum and target SDKs.

7. Speaking Java’s Language

compileOptions {
sourceCompatibility = JavaVersion.VERSION_X
targetCompatibility = JavaVersion.VERSION_X
}

Laying down the law in Java land. All modules, listen up: we’re targeting Java X!

8. Kotlin’s Own Special Touch

tasks.withType<KotlinJvmCompile>().configureEach {
kotlinOptions {
jvmTarget = JvmTarget.JVM_X.target
}
}

Because Kotlin won’t be left behind. Set your sights on JVM X, every Kotlin task in the house!

Voilà! With these snippets, your Android modules are now harmoniously marching to the beat of the same drum, perfectly in sync. No more manual reiterations. Just clean, shared settings that make your project setup as delightful as a well-made cup of coffee. Cheers to efficient Android project setups!

// Apply configurations to all sub-projects within the main project
subprojects {

// The 'afterEvaluate' block ensures configurations are applied after the project has been evaluated.
afterEvaluate {

// Check if the current project is extension-aware, to make sure we're dealing with an Android project.
(this as? ExtensionAware)?.extensions?.run {

// Specific configurations for the App module, helping to keep the app build script clean.
findByType<BaseAppModuleExtension>()?.run {

// Default configurations that apply to all build variants unless specifically overridden.
defaultConfig {
applicationId = "com.appName" // The unique identifier for the app
versionCode = 1 // Version code, incremented for each release.
versionName = "1.0" // User-visible version name.
}

// Defines how different build types (debug/release) should be set up.
buildTypes {
debug {
isDefault = true // Makes 'debug' the default build type.
isDebuggable = true // Enables debugging for this build type.
applicationIdSuffix = ".debug" // Adds a suffix to the applicationId for the debug build.
}
release {
isMinifyEnabled = true // Enables code minification.
isShrinkResources = true // Reduces unused resources.
isDebuggable = false // Disables debugging for release builds.
}
}
}

// Additional check to ensure we're configuring an Android project.
findByType<BaseExtension>()?.run {

// Sets the Android SDK version to be used for compiling the project.
setCompileSdkVersion(34)

// Sets the minimum and target SDK versions for the app.
defaultConfig {
minSdk = 34 // Minimum Android version the app can run on.
targetSdk = 26 // Android version the app is targeted towards.
}

// Sets compatibility for the compiled Java code.
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 // Source code is written using Java 17.
targetCompatibility = JavaVersion.VERSION_17 // Compiled bytecode is compatible with Java 17.
}
}
}

// Ensures Kotlin code is compiled to target JVM 17.
tasks.withType<KotlinJvmCompile>().configureEach {
kotlinOptions {
jvmTarget = JvmTarget.JVM_17.target
}
}
}
}

🎉 Bonus Section: Centralizing SDK Versions with libs.versions.toml 🎉

Alright, reader! Let’s sprinkle some extra magic onto our configuration. Well, brace yourself, because it’s about to get delightfully efficient up in here.

One best practice in the Android world is to centralize dependencies and versions, ensuring consistency and ease of updates across modules. We’ve already done some neat work with module configurations, but what about centralizing our SDK versions? Enter the libs.versions.toml file.

Here’s a snippet of what it might look like:

[versions]
sdk-compile = "34"
sdk-target = "34"
sdk-min = "26"

With this in place, we can effortlessly reference these values in our Kotlin build scripts. No more hunting down scattered SDK versions across your codebase!

To incorporate these versions, make the following changes:

setCompileSdkVersion(libs.versions.sdk.compile.get().toInt())

defaultConfig {
minSdk = libs.versions.sdk.min.get().toInt()
targetSdk = libs.versions.sdk.target.get().toInt()
}

Boom! Now your SDK versions are centralized, making future updates a breeze. Plus, your code is sleeker and more maintainable. You’ve just taken your Android project setup to a whole new level!

And to think, some folks just came here to copy-paste…

Till Our Paths Cross Again…

Tackling Android settings? At first, it felt like piecing together a jigsaw puzzle. From figuring out module configurations to nailing shared settings, it seemed daunting. But, with the right foundation, it became clear it’s not so hard after all. Say goodbye to CompileSdkVersion troubles and versioning mix-ups.

And if I might divert for a moment, have you ever felt like your project is a bit like Shrek’s journey? Just when you think you’ve settled into your coding swamp, along comes a ‘Donkey’ of a module, chattering away about configurations, and suddenly you’re off on another adventure! But remember, even in the most ogre-ish of times, there’s always a way, and maybe even a fairy tale ending.

A side note: Engineering thrives on collective wisdom. If you have insights or refinements to share, please do. I’m still navigating the ropes of this platform, so if there’s a comment section lurking somewhere, let’s have a chat there. If not, well, it’s just another quirk of being the new guy around here!

Until next time, may your code be bug-free and your coffee strong!

André

--

--

André Marques

Android Software Engineer at Sky - Peacock | SkyShowtime | Showmax