Configure and verify Android Gradle builds: Kotlin DSL (build.gradle.kts), version catalogs (libs.versions.toml), build types and product flavors, R8/ProGuard shrinking and keep rules, signing configs, and the local build/verify gate (./gradlew assembleDebug, testDebugUnitTest, lintDebug). Use when editing Gradle build files, adding dependencies, setting up flavors/variants, configuring minification or signing, or running the Android build/test/lint gate before reporting a change complete.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add eric-sabe/engsys --skill android-build-conventions --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Android Build Conventions?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/eric-sabe-android-build-conventions)More formats (shields.io, HTML) on the badges page.
---
name: android-build-conventions
description: "Configure and verify Android Gradle builds: Kotlin DSL (build.gradle.kts), version catalogs (libs.versions.toml), build types and product flavors, R8/ProGuard shrinking and keep rules, signing configs, and the local build/verify gate (./gradlew assembleDebug, testDebugUnitTest, lintDebug). Use when editing Gradle build files, adding dependencies, setting up flavors/variants, configuring minification or signing, or running the Android build/test/lint gate before reporting a change complete."
---
# Android Build Conventions
Configure, maintain, and verify Android builds with Gradle's Kotlin DSL targeting
AGP 8.x+ and Gradle 8.x+. Use the version catalog as the single source of truth
for dependencies, keep build logic declarative, and **always run the local gate**
before reporting an Android change complete — Android typically has no cloud CI
for the app build, so the local `./gradlew` run is the gate.
## Contents
- [Local Build / Verify Gate](#local-build--verify-gate)
- [Kotlin DSL Conventions](#kotlin-dsl-conventions)
- [Version Catalogs](#version-catalogs)
- [Build Types and Product Flavors](#build-types-and-product-flavors)
- [R8 / ProGuard](#r8--proguard)
- [Signing](#signing)
- [Convention Plugins](#convention-plugins)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
## Local Build / Verify Gate
Run these before reporting any Android change complete. They are fast, local, and
the de-facto gate (there is no cloud CI for the app build):
```
./gradlew assembleDebug # compile + package the debug APK
./gradlew testDebugUnitTest # run JVM unit tests (src/test)
./gradlew lintDebug # Android Lint static analysis
```
Naturalize the module if the app isn't the root project (e.g.
`./gradlew :app:assembleDebug`). The `pre-push-gradle.sh` hook in this pack runs
this gate automatically when Android files change on push — **do not bypass it
with `--no-verify`.**
Notes:
- Use the project's `./gradlew` wrapper, never a globally installed `gradle`.
- Instrumented tests (`connectedDebugAndroidTest`) need an emulator/device and are
**not** part of the fast gate — run them on demand.
- For a deeper check before release, add `./gradlew assembleRelease` (exercises
R8/minification).
## Kotlin DSL Conventions
Use `build.gradle.kts` (Kotlin DSL), not Groovy, for new build files.
```kotlin
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) // Compose compiler plugin (Kotlin 2.0+)
}
android {
namespace = "com.example.app"
compileSdk = 35
defaultConfig {
applicationId = "com.example.app"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures { compose = true }
kotlin { jvmToolchain(17) } // pin the JDK toolchain
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.activity.compose)
testImplementation(libs.junit)
testImplementation(libs.turbine)
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
}
```
- Declare the Compose BOM with `platform(...)` so all Compose artifacts share one
version. Apply the Compose compiler plugin (`org.jetbrains.kotlin.plugin.compose`)
on Kotlin 2.0+.
- Pin the JDK with `jvmToolchain(17)` (or the project's standard) for reproducible
builds across machines.
- Prefer `implementation` over `api` unless the dependency is part of a module's
public API — it keeps the build graph fast.
## Version Catalogs
Keep all versions, libraries, and plugins in `gradle/libs.versions.toml`. This is
the single source of truth; never hardcode versions in `build.gradle.kts`.
```toml
[versions]
agp = "8.7.0"
kotlin = "2.1.0"
composeBom = "2025.01.00"
coroutines = "1.9.0"
turbine = "1.2.0"
[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.9.3" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
junit = { group = "junit", name = "junit", version = "4.13.2" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
```
- Reference libraries as `libs.androidx.compose.material3` and plugins as
`alias(libs.plugins.android.application)`.
- Use `[bundles]` to group related dependencies that are always added together.
- BOM-managed artifacts (Compose) omit a version — the BOM resolves it.
## Build Types and Product Flavors
`buildTypes` vary how the **same** code is built (debug vs release). `flavors`
vary **what** is built (free vs paid, dev vs prod backend). Their product is a
build *variant* (e.g. `freeDebug`).
```kotlin
android {
buildTypes {
debug {
applicationIdSuffix = ".debug" // install alongside release
isDebuggable = true
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
signingConfig = signingConfigs.getByName("release")
}
}
flavorDimensions += "environment"
productFlavors {
create("dev") { dimension = "environment"; applicationIdSuffix = ".dev" }
create("prod") { dimension = "environment" }
}
}
```
- Use `applicationIdSuffix` on debug/dev so multiple variants coexist on a device.
- Keep flavor-specific code/resources in `src/<flavor>/` source sets.
- Put environment config (base URLs, keys) in `buildConfigField` or resource
overlays, not in code branches.
## R8 / ProGuard
R8 is the default shrinker/optimizer/obfuscator for release builds (replaces
ProGuard but reads the same `proguard-rules.pro` keep rules).
- Enable for release: `isMinifyEnabled = true` and `isShrinkResources = true`.
- Add **keep rules** for anything accessed reflectively: serialization models,
JNI, code referenced only by manifest/XML, libraries that need them.
```proguard
# Keep models used by kotlinx.serialization / Gson reflection.
-keep,allowobfuscation class com.example.app.model.** { *; }
# Keep enums used reflectively.
-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }
```
- Most modern libraries ship **consumer keep rules**, so you usually need few
manual rules — add them only when release builds crash where debug doesn't.
- Always test a **release** build before shipping; minification bugs don't appear
in `assembleDebug`.
- Enable obfuscation mapping retention so crash reports can be de-obfuscated.
## Signing
- **Never commit keystores or passwords.** Inject signing credentials from
`local.properties`, environment variables, or a secrets manager.
```kotlin
val keystoreProps = Properties().apply {
val f = rootProject.file("keystore.properties")
if (f.exists()) load(f.inputStream())
}
android {
signingConfigs {
create("release") {
storeFile = keystoreProps["storeFile"]?.let { file(it as String) }
storePassword = keystoreProps["storePassword"] as String?
keyAlias = keystoreProps["keyAlias"] as String?
keyPassword = keystoreProps["keyPassword"] as String?
}
}
}
```
- Add `keystore.properties` and `*.keystore`/`*.jks` to `.gitignore`.
- Debug builds use the auto-generated debug keystore — fine for local work.
## Convention Plugins
For multi-module projects, extract shared build logic into convention plugins in
`build-logic/` (composite build) rather than copy-pasting config across modules.
This keeps module `build.gradle.kts` files small and consistent. Avoid the legacy
top-level `allprojects {}`/`subprojects {}` blocks.
## Common Mistakes
1. **Hardcoded versions** in `build.gradle.kts` — use the version catalog.
2. **Groovy DSL for new files** — use Kotlin DSL (`.kts`).
3. **Not running the local gate** before reporting done — always
`assembleDebug` + `testDebugUnitTest` + `lintDebug`.
4. **Using a system `gradle`** instead of `./gradlew`.
5. **Committing keystores/passwords** — inject them; gitignore secrets.
6. **Shipping without testing a release build** — R8 issues only surface there.
7. **Missing keep rules** for reflective code — release crashes, debug fine.
8. **`api` everywhere** — leaks the dependency graph and slows builds; default to
`implementation`.
9. **Mismatched Compose compiler / Kotlin versions** — apply the Compose compiler
plugin matched to the Kotlin version (Kotlin 2.0+).
10. **No JDK toolchain pin** — builds differ across machines; set `jvmToolchain`.
## Review Checklist
- [ ] Build files use Kotlin DSL (`.kts`)
- [ ] All versions/libs/plugins live in `gradle/libs.versions.toml`
- [ ] Compose BOM declared via `platform(...)`; compiler plugin matched to Kotlin
- [ ] `release` build enables R8 (`isMinifyEnabled`, `isShrinkResources`)
- [ ] Keep rules present for reflective/serialized types
- [ ] Signing credentials injected, not committed; keystores gitignored
- [ ] JDK toolchain pinned (`jvmToolchain`)
- [ ] Local gate run: `assembleDebug` + `testDebugUnitTest` + `lintDebug`
- [ ] A release build was assembled before shipping
- [ ] Variants coexist on device (`applicationIdSuffix` on debug/dev)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!