As an Android project collects more and more modules, we find ourselves duplicating lots of boilerplate in our Gradle build files. This duplicate boilerplate wastes time, makes it more difficult to apply future changes, and increases our chances of introducing errors. Thankfully it is fairly simple to reduce this duplicate boilerplate.
Gradle build files for our modules have some unique settings like plugins and dependencies, but they also have lots of configuration options that are consistent across all modules. Most of our modules will specify a compile sdk version, minimum sdk version, test instrumentation runners, JVM target version, and perhaps lint options, packaging options, test options, and more. These are often identical across modules, so wouldn’t it be great if we could just write these once and share it across all of our modules? Here’s how.
In your project-level build.gradle.kts
file, add an extension function BaseExtension.baseConfig()
. This is where we will configure the settings that would have been inside the android {}
block of our Android modules.
fun BaseExtension.baseConfig() { | |
compileSdkVersion(AppConfig.compileSdk) | |
defaultConfig.apply { | |
minSdk = AppConfig.minSdk | |
targetSdk = AppConfig.targetSdk | |
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | |
} | |
compileOptions.apply { | |
sourceCompatibility = AppConfig.CompileOptions.javaSourceCompatibility | |
targetCompatibility = AppConfig.CompileOptions.javaSourceCompatibility | |
} | |
tasks.withType<KotlinCompile> { | |
kotlinOptions { | |
jvmTarget = AppConfig.CompileOptions.kotlinJvmTarget | |
} | |
} | |
} |
To apply these we need to add a second extension function:
/** | |
* Apply configuration settings that are shared across all modules. | |
*/ | |
fun PluginContainer.applyBaseConfig(project: Project) { | |
whenPluginAdded { | |
when (this) { | |
is AppPlugin -> { | |
project.extensions | |
.getByType<AppExtension>() | |
.apply { | |
baseConfig() | |
} | |
} | |
is LibraryPlugin -> { | |
project.extensions | |
.getByType<LibraryExtension>() | |
.apply { | |
baseConfig() | |
} | |
} | |
} | |
} | |
} |
And finally we need to wire these up to our actual modules:
subprojects { | |
project.plugins.applyBaseConfig(project) | |
} |
Now your module-specific build files can look as simple as this
plugins { | |
id(Plugins.androidLibrary) | |
kotlin(Plugins.kotlinAndroid) | |
} | |
dependencies { | |
implementation(projects.core.logging) | |
implementation(Dependencies.hiltAndroid) | |
} |
And that’s it!
Boilerplate reduced ✅
Follow for more on best practices in Kotlin and Android development.
This article was originally published on proandroiddev.com on March 05, 2022