One android defaultConfig to rule them all
Configure Android plugins once for all project sub-modules
The struggle is real. To improve on build speed, Android projects modularize into many small sub-modules. Some of these might be pure Java or Kotlin libraries, not concerning themselves with the version of the Android framework. But every Android application, library or feature module should be configured to target and compile with an identical Android SDK version. The most common way to achieve this with the least effort is to define the versions once in the root project, and then referenced afterward within every other project. The extension container for project.ext
helps with it.
buildscript {
ext.versions = [
minSdk: 21,
compileSdk: 28,
]
}android {
compileSdkVersion versions.compileSdk
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.compileSdk
}
}
This is repetitive and has to be applied within every module using an Android plugin. As software developers try to avoid repetition, this still seems to be suboptimal. Of course it’s a one time setup of the build system, but isn’t there a way to achieve the same without any repetition?
Gradle provides the subprojects
script block to configure these from the parent or root project. This block gets executed just before the actual build file, therefore there’s no possibility to check for the android
extension being available. Instead, the plugins.withType(PluginType)
filter can be used to configure the project when a matching plugin is applied to it.
subprojects {
plugins.withType(com.android.build.gradle.BasePlugin) {
android {
compileSdkVersion 28 defaultConfig {
minSdkVersion 21
targetSdkVersion 28
}
}
}
}
Now, this information is clearly defined in exactly one place and doesn’t have to be duplicated anymore. Using the BasePlugin
as a target ensures that the given block is applied to every Android plugin extending from it. This includes the application, library or feature plugins. Also, since it’s only serving as a default, it provides the liberty to override any value within the build.gradle of the sub-module itself.
This pattern is extensible and can be used for other common configurations as well. Just imagine to automatically apply the kotlin-android
plugin with every Android plugin. It’s almost as if Kotlin is actually the default language to use then. Or the testInstrumentationRunner
could be set to default of androidx.test.runner.AndroidJUnitRunner
(with or without defining the dependency as well). The same is possible with tasks as well, so the test logger can be configured to be more verbose and show more details of an error by default.
subprojects {
tasks.withType(Test) {
testLogging {
showExceptions true
showStackTraces true
showCauses true
}
} plugins.withType(com.android.build.gradle.BasePlugin) {
project.apply plugin: 'kotlin-android' def runner = 'androidx.test.runner.AndroidJUnitRunner' android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28 testInstrumentationRunner runner
}
}
}
}
Now every single module has to concern itself with what it actually has to; not a single thing more.