How to Diagnose and Fix Jetpack Compose Performance Pitfalls
Learn how to identify and resolve performance issues in Jetpack Compose by using Layout Inspector, Stability Reports, and configuration files, understanding stable vs unstable parameters, applying strong skipping, and leveraging annotations and wrapper classes to achieve efficient UI recomposition.
In Android development, Jetpack Compose offers a concise API and elegant design for building UI, but hidden performance traps can arise when components are recomposed excessively.
1. The "magic" of Compose and performance pitfalls
Compose simplifies complex UI construction, yet over‑recomposition—such as a screen with three text fields causing all components to recompose on each input—can degrade performance. Developers should monitor real issues instead of premature optimization.
2. Performance diagnosis: tools
2.1 Layout Inspector: visualizing recomposition counts
Layout Inspector shows how many times each composable is recomposed; only interactive components should recompose under normal conditions.
Usage note: If the recomposition counter is missing, ensure the Compose version file is not excluded in build.gradle.
packagingOptions {
// Wrong: excludes the version file, Layout Inspector cannot show counts
exclude "META-INF/compose_compiler_version.txt"
}
// Correct: keep the version file
packagingOptions {
pickFirst "META-INF/compose_compiler_version.txt"
}2.2 Stability Reports: analyzing component stability
Beyond recomposition counts, Stability Reports indicate whether a composable's parameters are considered stable, which drives Compose's "smart recomposition".
Generate visual reports: Apply the compose-compiler-report-html plugin in build.gradle and run ./gradlew assembleDebug composeCompilerReport to produce an HTML report.
plugins {
id "io.github.zach-klippenstein.compose-compiler-report-html" version "1.0.0"
}The report highlights unstable parameters, e.g., InputScreenState.components marked red, revealing why unnecessary recompositions occur.
3. Understanding Compose's smart recomposition and stability rules
3.1 Core concept: stable vs unstable parameters
Stable parameters trigger recomposition only when their equals returns false—typical examples are primitive types (Int, String) or data classes with all val properties.
Unstable parameters cause their parent composable to recompose regardless of changes—examples include classes with var properties or unannotated custom classes.
3.2 Strong Skipping (enabled by default in Compose 1.0.20+)
Strong Skipping optimizes the handling of unstable parameters, removing the need to manually cache lambdas with remember.
// Before strong skipping: cache lambda manually
@Composable
fun OldButton(onClick: () -> Unit) {
val cachedOnClick = remember { onClick }
Button(onClick = cachedOnClick) { Text("Click") }
}
// After strong skipping: no manual caching needed
@Composable
fun NewButton(onClick: () -> Unit) {
Button(onClick = onClick) { Text("Click") } // No extra recomposition
}3.3 How Compose infers stability
Immutable types : primitive types, data classes with only val properties (including nested val fields).
Mutable types : classes containing var, or collections like List which are mutable by default in Java.
4. Practical optimization: four tools to solve stability issues
4.1 Upgrade toolchain: enable strong skipping
Update the Compose compiler and Kotlin version to 1.0.20+ in build.gradle:
dependencies {
// Compose BOM
implementation platform("androidx.compose:compose-bom:2023.10.01")
kapt "androidx.compose.compiler:compiler:1.5.3"
}
plugins {
id "org.jetbrains.kotlin.android" version "1.8.22"
}4.2 Global stability configuration file
Create compose-stability-config.txt under src/main and list types to treat as stable:
// Mark all classes in com.example.models as stable
com.example.models.** -> Stable
// Mark Kotlin List as stable (project uses immutable flow)
kotlin.collections.List -> Stable
// Mark ViewModel classes as stable
androidx.lifecycle.ViewModel -> StableReference the file in the module's build.gradle:
android {
buildFeatures { compose true }
composeOptions {
kotlinCompilerExtensionVersion "1.5.3"
stabilityConfigurationFile file("src/main/compose-stability-config.txt")
}
}4.3 Manual annotations
When configuration files are insufficient, annotate classes with @Immutable or @Stable:
// Immutable data class
@Immutable
data class TextInputModel(val id: String, val value: String)
// Stable ViewModel
@Stable
class InputViewModel : ViewModel() {
private val _state = mutableStateOf(InputScreenState(emptyList()))
val state: State<InputScreenState> = _state
}4.4 Wrapper classes for special cases
For types like ByteArray that lack proper equals, wrap them in a data class and implement equality manually:
// Wrapper with custom equals and hashCode
data class GrpcResponseWrapper(val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GrpcResponseWrapper
return data.contentEquals(other.data)
}
override fun hashCode(): Int = data.contentHashCode()
}5. Validation and team collaboration
5.1 Effect verification
Layout Inspector : After optimization, only the interacting TextField should show increased recomposition counts.
Stability Report : Parameters like InputScreenState.components and UIComponent.TextInput should appear green (stable), indicating no redundant recompositions.
5.2 Team guidelines
Code review : Ensure data models follow the "all val " rule; prohibit var in designated packages.
Documentation : Record stability configuration rules in project docs to guide new contributors.
6. Summary: three‑step Compose performance optimization
Measure : Use Layout Inspector and Stability Reports to locate unstable parameters.
Optimize : Apply global stability config, enable strong skipping, and use wrapper classes where needed.
Validate : Re‑run tools to confirm reduced recompositions and enforce team standards.
AndroidPub
Senior Android Developer & Interviewer, regularly sharing original tech articles, learning resources, and practical interview guides. Welcome to follow and contribute!
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.
