android-version-catalog
Skill18 starsChanged 4 months agoMITView on GitHub
Gradle version catalog management and convention plugin creation for Android/KMP - adding libraries, plugins, bundles, and version refs in libs.versions.toml, plus creating convention plugins for libraries with non-trivial Gradle config (KSP, compiler plugins, multi-artifact setup). Use this skill whenever adding a dependency, creating a bundle, registering a plugin in the version catalog, creating a convention plugin, looking up the latest version of a library, or updating a dependency version. Trigger on phrases like "add a library", "add a dependency", "version catalog", "libs.versions.toml", "bundle", "add a plugin", "convention plugin", "latest version of", "update dependency", "create a convention plugin", "add Ktor", "add Room", or "add KSP".
In this repository67 other files it gives its agents.
- agent-onboardingSkill
- android-backgroundSkill
- android-cliSkill
- android-compose-architectureSkill
- android-compose-componentsSkill
- android-data-layerSkill
- android-di-koinSkill
- android-error-handlingSkill
- android-integration-testingSkill
- android-jacoco-setupSkill
- android-jvm-test-coverageSkill
- android-module-structureSkill
- android-navigationSkill
- android-presentation-mviSkill
- android-preview-figma-verifySkill
- android-ui-screenshot-verifySkill
- android-ui-testingSkill
- android-ui-verifySkill
- android-unit-testingSkill
- architecture-decision-recordSkill
- careful-planningSkill
- compose-adaptive-layoutsSkill
- figma-to-android-vectorSkill
- finalizeSkill
- git-commitSkill
- handoffSkill
- kmp-compose-architectureSkill
- kmp-compose-componentsSkill
- kmp-data-layerSkill
- kmp-di-koinSkill
- kmp-error-handlingSkill
- kmp-integration-testingSkill
- kmp-migration-analyzerSkill
- kmp-module-structureSkill
- kmp-navigationSkill
- kmp-presentation-mviSkill
- kmp-ui-testingSkill
- kmp-unit-testingSkill
- kotlin-coroutinesSkill
- kotlin-flowsSkill
- kotlin-mutation-testingSkill
- openspec-artifact-review-lightSkill
- openspec-artifact-reviewSkill
- project-breakdownSkill
- react-data-layerSkill
- react-error-handlingSkill
- react-integration-testingSkill
- react-presentation-mviSkill
- react-project-structureSkill
- react-routingSkill
- react-screen-architectureSkill
- react-securitySkill
- react-server-stateSkill
- react-ui-componentsSkill
- react-unit-testingSkill
- speckit-parallelize-agent-teamsSkill
- speckit-parallelize-subagentsSkill
- spring-api-layerSkill
- spring-di-configurationSkill
- spring-integration-testingSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 7 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 81
If two libraries would collide on artifactId alone, prefix with enough of the group to disambiguate.
- Distinctive: line 85
Use the shortest unambiguous name for the library family:
- Distinctive: line 111
Create a bundle when 2+ artifacts from the same library are always added together:
- Distinctive: line 125
Do not create a bundle for a single artifact.
- Distinctive: line 148
When a library involves non-trivial Gradle config — KSP processing, compiler plugins, BOM management, platform-specific setup, or config …
- Distinctive: line 167
Every convention plugin project needs this accessor to read the version catalog:
- Very specific: line 301
If the library is a simple implementation dependency with no special Gradle config, a convention plugin is unnecessary — just add it to the …
- Distinctive: line 311
Create a bundle: if multiple artifacts from this library will always be used together.
- Distinctive: line 322
Important: Always look up every version on mvnrepository.com — never rely on version numbers from memory or from this skill's examples, as …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Android / KMP Gradle Version Catalog & Convention Plugins
Principles
- All versions are named references — every version lives in
[versions]with a named key. Never inline a version string directly in a library or plugin definition. - Kebab-case everywhere — aliases for versions, libraries, bundles, and plugins all use kebab-case.
- Single source of truth —
gradle/libs.versions.tomlis the only place versions and coordinates are declared. No hardcoded versions in build files. - Bundles for cohesive sets — when multiple artifacts from the same library are always used together, group them in
[bundles]. - All plugins live in
[plugins]— both external Gradle plugins (withversion.ref) and convention plugins from:build-logic(withversion = "unspecified"). - Suggest convention plugins for non-trivial config — when a library involves KSP, compiler plugins, BOM, platform-specific setup, or config that would be copy-pasted across modules, suggest creating a convention plugin.
- Keep related versions in sync — when adding a library that depends on KSP, Kotlin, or other toolchain dependencies, also check and update those versions to the latest compatible release. For example, adding Room requires checking that the KSP version matches the current Kotlin version.
Version Catalog Structure
[versions]
ktor = "3.1.1"
koin = "4.0.4"
room = "2.7.2"
ksp = "2.1.20-1.0.32"
kotlin = "2.1.20"
compose-multiplatform = "1.7.3"
[libraries]
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
[bundles]
ktor = [
"ktor-client-core",
"ktor-client-content-negotiation",
"ktor-client-auth",
"ktor-client-logging",
"ktor-serialization-kotlinx-json",
]
[plugins]
# External plugins — real versions via version.ref
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
room = { id = "androidx.room", version.ref = "room" }
# Convention plugins — version is always "unspecified"
convention-android-application = { id = "com.plcoding.convention.android.application", version = "unspecified" }
convention-cmp-feature = { id = "com.plcoding.convention.cmp.feature", version = "unspecified" }
convention-kmp-library = { id = "com.plcoding.convention.kmp.library", version = "unspecified" }
convention-room = { id = "com.plcoding.convention.room", version = "unspecified" }Naming Conventions
Library Aliases
Derive from the Maven artifactId, keeping kebab-case as-is:
| Maven coordinate | Alias |
io.ktor:ktor-client-core | ktor-client-core |
io.insert-koin:koin-core | koin-core |
androidx.room:room-runtime | room-runtime |
org.jetbrains.kotlinx:kotlinx-coroutines-core | kotlinx-coroutines-core |
io.coil-kt.coil3:coil-compose | coil-compose |
If two libraries would collide on artifactId alone, prefix with enough of the group to disambiguate.
Version Keys
Use the shortest unambiguous name for the library family:
| Library family | Version key |
| Ktor | ktor |
| Koin | koin |
| AndroidX Room | room |
| Kotlin | kotlin |
| KSP | ksp |
| Compose BOM | compose-bom |
| Compose Multiplatform | compose-multiplatform |
Full Naming Summary
| Thing | Format | Example |
| Version key | Shortest unambiguous kebab-case | ktor, room, compose-bom |
| Library alias | Kebab-case from artifactId | ktor-client-core, room-runtime |
| Bundle alias | Kebab-case library family name | ktor, koin |
| External plugin alias | Kebab-case from plugin name | ksp, kotlin-serialization |
| Convention plugin alias | convention-<short-name> | convention-room, convention-cmp-feature |
Bundles
Create a bundle when 2+ artifacts from the same library are always added together:
[bundles]
ktor = ["ktor-client-core", "ktor-client-content-negotiation", "ktor-client-auth"]
koin = ["koin-core", "koin-android", "koin-androidx-compose"]Usage in build.gradle.kts:
implementation(libs.bundles.ktor)Do not create a bundle for a single artifact.
External Plugins
External Gradle plugins use real versions with version.ref:
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }plugins {
alias(libs.plugins.ksp)
}Convention Plugins
When a library involves non-trivial Gradle config — KSP processing, compiler plugins, BOM management, platform-specific setup, or config that would be duplicated across modules — suggest creating a convention plugin. This keeps feature module build files clean and ensures consistent configuration.
Directory Structure
build-logic/
└── convention/
├── build.gradle.kts ← gradlePlugin { } registration
└── src/main/kotlin/
├── RoomConventionPlugin.kt ← plugin classes at package root
└── com/plcoding/<project>/convention/
├── ProjectExt.kt ← val Project.libs accessor
├── KotlinAndroid.kt ← shared Android config
├── KotlinMultiplatform.kt ← shared KMP config
└── AndroidCompose.kt ← shared Compose configProjectExt.kt (required utility)
Every convention plugin project needs this accessor to read the version catalog:
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
val Project.libs: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")Plugin Class Pattern
Convention plugins implement Plugin<Project>, apply other plugins, and configure the project. They pull dependencies from the version catalog — never hardcode coordinates.
Example — Room (KMP):
import com.plcoding.chirp.convention.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import androidx.room.gradle.RoomExtension
import com.google.devtools.ksp.gradle.KspExtension
class RoomConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.google.devtools.ksp")
pluginManager.apply("androidx.room")
extensions.configure<RoomExtension> {
schemaDirectory("$projectDir/schemas")
}
dependencies {
"commonMainImplementation"(libs.findLibrary("room-runtime").get())
"commonMainImplementation"(libs.findLibrary("sqlite-bundled").get())
"kspAndroid"(libs.findLibrary("room-compiler").get())
"kspIosSimulatorArm64"(libs.findLibrary("room-compiler").get())
"kspIosArm64"(libs.findLibrary("room-compiler").get())
"kspIosX64"(libs.findLibrary("room-compiler").get())
"kspDesktop"(libs.findLibrary("room-compiler").get())
}
}
}
}Example — Android-only library (Compose):
class ComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("org.jetbrains.kotlin.plugin.compose")
extensions.configure<com.android.build.api.dsl.CommonExtension<*, *, *, *, *, *>> {
buildFeatures {
compose = true
}
}
dependencies {
val bom = libs.findLibrary("compose-bom").get()
"implementation"(platform(bom))
"androidTestImplementation"(platform(bom))
}
}
}
}Plugin Layering
Convention plugins can compose by applying other convention plugins. Each layer adds its specific config on top:
cmp-feature → applies cmp-library → applies kmp-library
(Koin, (Compose, (KMP targets,
Navigation, Compose compiler) Serialization)
shared deps)This avoids duplication — a feature module only applies one plugin and inherits all layers.
Registration
Register each plugin in build-logic/convention/build.gradle.kts:
gradlePlugin {
plugins {
register("room") {
id = "com.plcoding.convention.room"
implementationClass = "RoomConventionPlugin"
}
register("cmpFeature") {
id = "com.plcoding.convention.cmp.feature"
implementationClass = "CmpFeatureConventionPlugin"
}
}
}Detect the namespace prefix (e.g., com.plcoding.convention) from the existing convention plugins in the project.
Version Catalog Entry
Convention plugins always use version = "unspecified" — they are built locally, not fetched from a repository:
[plugins]
convention-room = { id = "com.plcoding.convention.room", version = "unspecified" }Applied in a feature module:
plugins {
alias(libs.plugins.convention.room)
}When to Suggest a Convention Plugin
Suggest creating one (don't force) when the library involves:
- KSP annotation processing (Room, Moshi, Hilt)
- Compiler plugins (Compose, Serialization)
- Complex multi-artifact setup with BOM
- Platform-specific configurations (KMP targets needing different KSP configs)
- Config that would be copy-pasted across 2+ modules
If the library is a simple implementation dependency with no special Gradle config, a convention plugin is unnecessary — just add it to the version catalog.
Workflow: Adding a New Library
- Look up the latest stable version on mvnrepository.com. Prefer stable releases. Only use RC/beta if the user explicitly asks or no stable version exists yet.
- Check related toolchain versions — if the library depends on KSP, verify the KSP version is compatible with the project's Kotlin version. If not, update both. Always look up the latest stable KSP and Kotlin versions when they are involved. The KSP version format is
<kotlin-version>-<ksp-version>(e.g.,2.1.20-1.0.32), so when Kotlin updates, KSP must update too. - Add or reuse a version entry in
[versions]. Use the shortest unambiguous key for the library family. - Add library entries in
[libraries]withmoduleandversion.ref. Alias = kebab-case from the artifactId. - Create a bundle if multiple artifacts from this library will always be used together.
- Add a Gradle plugin entry in
[plugins]withversion.refif the library ships a Gradle plugin (KSP, Room, Serialization, etc.). - If the library involves non-trivial Gradle config, suggest creating a convention plugin:
a. Create the plugin class in
build-logic/convention/src/main/kotlin/b. Register inbuild-logic/convention/build.gradle.ktsviagradlePlugin { plugins { register(...) } }c. Add aconvention-<name>entry in[plugins]withversion = "unspecified" - Reference in
build.gradle.kts:- Libraries:
implementation(libs.<alias>)orimplementation(libs.bundles.<bundle>) - Plugins:
alias(libs.plugins.<alias>)
- Libraries:
- Sync Gradle to verify resolution.
Important: Always look up every version on mvnrepository.com — never rely on version numbers from memory or from this skill's examples, as they become outdated quickly.
Checklist
Adding a Dependency
- All versions looked up on mvnrepository.com — not from memory or skill examples
- Related toolchain versions (KSP, Kotlin) checked and updated if needed
- Version entry exists in
[versions]— no inline versions anywhere - Library alias uses kebab-case derived from the artifactId
- Library definition uses
version.ref, not a hardcoded version string - Bundle created if 2+ artifacts from the same library are always used together
- External Gradle plugin added to
[plugins]withversion.refif applicable build.gradle.ktsuses typed accessors (libs.<alias>,libs.bundles.<alias>,libs.plugins.<alias>)
Creating a Convention Plugin (when applicable)
- Plugin class implements
Plugin<Project>and applies relevant plugins - Dependencies pulled from version catalog via
libs.findLibrary(...)— no hardcoded coordinates - Plugin registered in
build-logic/convention/build.gradle.kts - Convention plugin entry in
[plugins]withversion = "unspecified" - Namespace matches existing convention plugins in the project
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

