Android build guide

Android Build and Dependencies

Configure Gradle version catalogs, convention plugins, SDK levels, optimization, and signing for modern Material Design 3 and Material 3 Expressive applications.

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

Pinning Expressive Alpha APIs

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