Version catalog
Centralized dependency definition (libs.versions.toml)
Declare all dependency coordinates, plugin IDs, and versions in your version catalog:
[versions]
# Build toolchain & plugins
agp = "9.3.1"
kotlin = "2.4.10"
# AndroidX platform & lifecycle
coreKtx = "1.19.0"
appcompat = "1.7.0"
activityCompose = "1.13.0"
lifecycleRuntimeKtx = "2.11.0"
# Jetpack Compose & Material 3
composeBom = "2026.08.00"
composeMaterial3 = "1.5.0-alpha26"
composeMaterial3Adaptive = "1.3.0"
# Navigation, persistence, and serialization
navigationCompose = "2.9.8"
datastore = "1.2.1"
kotlinxSerialization = "1.11.0"
kotlinxCoroutines = "1.11.0"
# Testing
junit4 = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
[libraries]
# Platform & Lifecycle
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
# Compose BOM & UI
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
# Material 3 & Expressive / Adaptive
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "composeMaterial3" }
androidx-compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "composeMaterial3Adaptive" }
androidx-compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "composeMaterial3Adaptive" }
androidx-compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "composeMaterial3Adaptive" }
androidx-compose-material3-adaptive-navigation-suite = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite", version.ref = "composeMaterial3" }
# Navigation & Architecture
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
# Testing
junit = { group = "junit", name = "junit", version.ref = "junit4" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
# Build plugins
android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Toolchain & SDKs
SDK levels and compiler options
- Compile SDK (37): Access latest Android platform APIs and Edge-to-Edge window insets.
- Minimum SDK (24): Supports Android 7.0+ across virtually all active devices.
- Target SDK (36): Declared in the application module to adhere to latest Android platform policies.
- Java / Kotlin Target (JVM 11): Consistent bytecode target across all modules.
- Compose Compiler: Bundled natively with Kotlin 2.0+ via
org.jetbrains.kotlin.plugin.compose.
Convention plugins
Modular Gradle configuration with build-logic
Application convention
Applies Android application plugin, common SDKs, packaging options, and build types.
Library convention
Applies Android library plugin, shared SDK settings, and namespace conventions.
Compose convention
Enables Compose build feature, compiler plugin, and adds Compose UI/Material3 dependencies.
Serialization convention
Applies Kotlin Serialization compiler plugin and JSON runtime dependency.
Dependency groups
BOM vs Explicit Pinned Versions
When using Material 3 Expressive features (SplitButton, FloatingToolbar, WavyProgressIndicator, NavigationSuiteScaffold), pin androidx.compose.material3 (e.g. 1.5.0-alpha26) directly rather than relying solely on the Compose BOM line.
Optimization & R8
Code shrinking, resource shrinking, and native symbols
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
}
}
packaging {
jniLibs.keepDebugSymbols += setOf(
"**/libandroidx.graphics.path.so",
"**/libdatastore_shared_counter.so"
)
}
}
Release signing
Secure local credentials
Read signing configuration from ignored signing.properties (or local.properties) without committing credentials:
signing.storeFile=/path/to/keystore.jks
signing.storePassword=yourStorePassword
signing.keyAlias=yourKeyAlias
signing.keyPassword=yourKeyPassword
Verification
Automated testing and build gates
.\gradlew.bat -p build-logic :convention:test
.\gradlew.bat testDebugUnitTest
.\gradlew.bat lintDebug
.\gradlew.bat :app:assembleDebug
.\gradlew.bat :app:assembleRelease