Using Custom Gradle Convention Plugins to Share Build Scripts in a Kotlin Multiplatform Android Project
This article demonstrates how to replace duplicated Gradle build scripts across multiple modules in a Kotlin Multiplatform Android project by creating custom convention plugins, leveraging Version Catalogs and the NowInAndroid example to centralize configuration, simplify third‑party plugin setup, and improve IDE assistance.
Background: The author migrated a personal KMP project to use the new Gradle Version Catalog (toml) and switched build scripts from build.gradle to build.gradle.kts . Multiple modules had similar build.gradle.kts files, and the previous approach of a shared base.gradle no longer provided IDE assistance.
Solution: Inspired by the NowInAndroid project, the author created custom Gradle convention plugins to encapsulate common configuration and avoid repetitive scripts.
NowInAndroid reference: The NowInAndroid build.gradle.kts is minimal, only applying plugins, setting a namespace, and declaring dependencies. Its build-logic folder defines convention plugins that serve as a single source of truth for module configuration.
plugins {
alias(libs.plugins.nowinandroid.android.feature)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco)
}
android {
namespace = "com.google.samples.apps.nowinandroid.feature.settings"
}
dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.google.oss.licenses)
implementation(projects.core.data)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}Basic workflow: Write a custom plugin class that implements Plugin<Project> , apply required plugins, and configure extensions (e.g., Room, BuildKonfig). Register the plugin in build-logic/build.gradle.kts with a unique ID, and expose it via libs.versions.toml .
class AndroidRoomConventionPlugin : Plugin
{
override fun apply(target: Project) {
with(target) {
pluginManager.apply("androidx.room")
pluginManager.apply("com.google.devtools.ksp")
extensions.configure
{
schemaDirectory("${projectDir}/schemas")
}
dependencies {
add("implementation", libs.findLibrary("room.runtime").get())
add("implementation", libs.findLibrary("room.ktx").get())
add("ksp", libs.findLibrary("room.compiler").get())
}
}
}
}Third‑party plugins: The author wrapped common third‑party plugin configurations (BuildKonfig, Libres, SqlDelight) into a single plugin ThirdPartyPluginsCP , registering it with ID transtation.kmp.thirdpartyplugins and adding it to the version catalog.
class ThirdPartyPluginsCP : Plugin
{
override fun apply(target: Project) {
with(target) {
setupLibres()
setupBuildKonfig()
setupSqlDelight()
}
}
}Common KMP configuration: The author created functions like setupCommonKMP to apply the Kotlin Multiplatform and Compose plugins, configure Android target options, source sets, and dependencies, then used these functions in library and application convention plugins.
fun Project.setupCommonKMP(android: CommonExtension<*, *, *, *, *>) {
pluginManager.apply("kotlin-multiplatform")
pluginManager.apply("org.jetbrains.compose")
// configure kotlin options, source sets, android namespace, etc.
}Result: By consolidating build logic into convention plugins, the project’s module build scripts become concise, e.g., applying only the necessary plugin IDs and a few module‑specific settings, dramatically reducing duplication and improving maintainability.
Source code: https://github.com/FunnySaltyFish/Transtation-KMP
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.