Image from Jetbrains blog article
Many of us have faced Groovy difficulties and used to convert it to Kotlin DSL. Then, as Android engineers, working on a project entirely written in Kotlin is pure joy.
We assume going on a feature-based modularized app structure applying the version catalog feature. We would adequately manage the build logic to make it centralized and reusable by the modules.
Here are the steps to facilitate your Gradle build configurations into a centralized build logic by creating your convention plugin scripts.
The typical way we all used was to place the custom scripts into the default buildSrc
module. Previously, buildSrc
implementation was not recommended because of invalidating the build cache and synchronizing the whole project with every change to the dependencies.
It’s now treated as an included build in Gradle 8.
Project level declaration
In the project settings script, we move the configuration scripts into a build-logic
composite build module.
The first pluginManagement
block ensures Gradle uses suitable repositories for the plugins. We include our new module in the build and specify explicitly the repositories we need.
Another key element is the mode in the centralized dependency declarations that you would declare to enforce Gradle always to get the correct dependencies regarding the resolution mode. In this example, we would use the repositories stated in the project settings.
@file:Suppress("UnstableApiUsage") | |
enableFeaturePreview("VERSION_CATALOGS") | |
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") | |
pluginManagement { | |
includeBuild("build-logic") | |
repositories { | |
gradlePluginPortal() | |
google() | |
mavenCentral() | |
} | |
} | |
dependencyResolutionManagement { | |
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) | |
repositories { | |
google() | |
mavenCentral() | |
} | |
} | |
rootProject.name = "GradleKotlinConventionPlugins" | |
include(":app") |
Project level settings script
Shared logic composite module
We start the new composite module by enabling the version catalog feature in the settings script.
@Suppress("UnstableApiUsage") | |
dependencyResolutionManagement { | |
versionCatalogs { | |
create("libs") { | |
from(files("../gradle/libs.versions.toml")) | |
} | |
} | |
} | |
rootProject.name = "build-logic" |
Build logic module settings script
Now, we define our new module as a Kotlin script by enabling the Kotlin DSL and forcing the precompiled script plugins to make it part of the Kotlin source set like any other class.
Henceforth, the composite module as part of the project build is responsible for the configuration, which we usually do in the project-level Gradle build script. We move there the Gradle-specific declarations.
plugins { | |
`kotlin-dsl` | |
`kotlin-dsl-precompiled-script-plugins` | |
} | |
repositories { | |
google() | |
mavenCentral() | |
gradlePluginPortal() | |
} | |
dependencies { | |
implementation(libs.kotlin.gradlePlugin) | |
implementation(libs.gradle) | |
} | |
tasks.test { | |
useJUnitPlatform() | |
} |
Build logic module build script
Type-based conventions
In a feature-based modularized app structure, we have the main app module, the feature modules, and the shared modules for some reusable logic or data, for example.
The main convention plugins we identify :
- Kotlin library convention
- Android library convention
- Android composable library convention
Kotlin library convention
Kotlin library convention is a plugin we usually use to declare some APIs or to share some logic. It is an overridden Java library where we target the JDK version.
plugins { | |
`java-library` | |
kotlin("jvm") | |
} | |
kotlin { | |
jvmToolchain(jdkVersion = 11) | |
} | |
task("testUnitTest") { | |
dependsOn("test") | |
} |
Kotlin library conventions plugin script
Kotlin Android library convention
Kotlin Android library convention is applied by the feature modules with ViewBinding feature activated by default.
plugins { | |
id("com.android.library") | |
kotlin("android") | |
} | |
@Suppress("UnstableApiUsage") | |
android { | |
. . . | |
compileOptions { | |
isCoreLibraryDesugaringEnabled = true | |
targetCompatibility = JavaVersion.VERSION_11 | |
sourceCompatibility = JavaVersion.VERSION_11 | |
} | |
kotlinOptions { | |
jvmTarget = JavaVersion.VERSION_11.toString() | |
} | |
buildFeatures { | |
viewBinding = true | |
} | |
} | |
dependencies { | |
. . . | |
} |
Kotlin Android library conventions plugin script
Job Offers
Kotlin Android composable library convention
It is an extension of the previous library convention with the Jetpack Compose-related declarations.
plugins { | |
id("kotlin-android-library-conventions") | |
} | |
@Suppress("UnstableApiUsage") | |
android { | |
buildFeatures.compose = true | |
composeOptions.kotlinCompilerExtensionVersion = libs.versionKotlinCompiler | |
} | |
dependencies { | |
implementation(platform(libs.libAndroidxComposeBom)) | |
implementation(libs.libAndroidxComposeFoundation) | |
} |
Kotlin Android library conventions with Jetpack Compose plugin script
When we create a new module, it is much easier to define the build script by applying the convention plugins and the additional dependencies.
We could also consider applying the composite module to another project.
plugins { | |
id("kotlin-android-library-conventions") | |
id("test-conventions") | |
} | |
dependencies { | |
implementation(libs.androidX.appCompat) | |
implementation(libs.google.material) | |
} |
Sample feature build script applying custom convention plugins
Conclusion
In conclusion, modularization is our main concern for the ever-growing projects to solve many problems we can enumerate: codebase size, scalability, readability, code quality, code duplication, etc.
We usually and naturally split the project into feature-oriented modules by isolating the feature code.
Google Android team has also recently published a guide to app modularization with the same approach.
There are some constraining issues related to Version Catalog.
The libraries are not directly accessible, and we need to create some extension functions to solve it.
And the plugins are not recognized in the plugins block. The issue seems fixed in Gradle 8.
You can find all of the source code in my Github conventions repository here.
This article was previously published on proandroiddev.com