🧩 Mastering Stability in Jetpack Compose
Why your UI skips (or flickers) and how to fix it using the Smart Porch Light analogy, Kotlinx Immutable Collections, and Compiler Metrics.
If you’ve spent any time building with Jetpack Compose, you’ve likely encountered two terms that sound like they belong in a chemistry lab: Stable and Unstable.
Understanding these isn’t just about theory; it’s the secret sauce to fixing “janky” UI and ensuring your app runs at a buttery-smooth 60 or 120 FPS. Let’s break down how the Compose compiler thinks.
🧠 The “Smart Porch Light” Analogy
Imagine you have a motion-sensor porch light.
- Stable (Smart Sensor): The light only turns on when it detects a human-sized object moving. If a leaf blows by, it stays off. It knows exactly what “change” looks like.
- Unstable (Broken Sensor): The light flickers every time the wind blows because it’s not sure if that was a person or just a breeze. To be “safe,” it just keeps turning on.
In Compose, Recomposition is that light. Stability tells Compose whether it can safely keep the light off (skip re-executing) or if it needs to turn it on “just in case.”
The Rule of Thumb: Stability does not cause recomposition; it enables skipping. Unstable parameters force Compose to “play it safe” and recompose even if nothing changed.
🟢 Stable Types: The “Skippers”
When a type is Stable, Compose can reason about its equality. If the inputs to a Composable are the same as they were during the last frame, Compose skips re-executing the Composable to save processing power.
What counts as Stable?
- Primitives & Strings:
Int,Boolean,String, etc. - State<T>: Inherently stable because Compose explicitly tracks the
.valueproperty. - Lambdas: Generally treated as stable, though they can still cause issues if they capture unstable values.
- Data Classes with
val: IF (and only if) every property inside is also stable.
💻 Kotlin Example: The Immutable Profile
// 🟢 This is Stable
data class UserProfile(
val username: String, // String is stable
val bio: String // String is stable
)
@Composable
fun ProfileHeader(user: UserProfile) {
// If 'user' has the same values, Compose SKIPS this block.
// Compose uses structural equality (equals()) for stable types.
Text(text = user.username)
}🔴 Unstable Types: The “Play-it-Safe” Crew
When a type is Unstable, Compose doesn’t trust that it hasn’t changed internally. It defaults to referential equality, meaning unless it’s the exact same instance, it assumes it’s dirty.
What counts as Unstable?
- Classes with
var: If a property can change without Compose knowing, the whole class is unstable. - Mutable Collections:
ArrayListorMutableListare unstable because the contents can change while the object reference stays the same. - Interfaces (like
List<T>): Paradoxically, standard Lists are often treated as unstable. SinceListis an interface, Compose cannot guarantee at compile-time that the underlying implementation isn't actually aMutableListin disguise.
💻 Kotlin Example: The “Fake” Immutable Class
// 🔴 STILL UNSTABLE (even with all 'val')
data class UserTags(
val name: String,
val tags: List<String> // 🚨 List is an interface, so this class is Unstable!
)
@Composable
fun TagCloud(data: UserTags) {
// Compose will RECOMPOSE this every time the parent does,
// because it can't guarantee 'tags' hasn't changed.
data.tags.forEach { Text(it) }
}🛠️ Advanced Fix: Kotlinx Immutable Collections
To fix the “List problem,” use Kotlinx Immutable Collections. These are explicitly modeled for structural sharing and are recognized by the compiler as stable.
1. Add the Dependency:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8")
}2. Implementation:
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
// 🟢 STABLE: Now Compose can safely skip
data class StableUserList(
val users: ImmutableList<String>
)⚠️ The @Immutable Warning
You can “force” stability using annotations, but it comes with a contract.
@Immutable
data class StableWrapper(val item: ExternalUser)Expert Warning:
@Immutableis a promise, not a magic spell. If you annotate a class that actually containsvarproperties or mutable lists, you are "lying" to the compiler. This can lead to stale UI bugs where your data changes but the screen doesn't update because Compose "skipped" it based on your promise.
🔍 How to Verify Stability (The Pro Way)
Don’t guess — check the Stability Report.
- Enable the report in your
build.gradle:
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
}2. Run a build and check classes.txt. You’ll see:
stable class MyData→ High five! This is skippable.unstable class MyData→ This is your performance bottleneck.
Frequently Asked Questions (FAQs)
Does val automatically make a data class stable?
No. A data class is only stable if all its properties are also stable. A val property pointing to a List or a var-filled class makes the whole data class unstable.
Why is List unstable but String stable?
String is a final, immutable class. List is an interface. Compose plays it safe with interfaces because it cannot guarantee immutability at compile time.
How does equality differ between the two?
Compose generally uses structural equality (.equals()) for stable types to determine if it should skip. For unstable types, it relies on referential equality, often failing to skip because new instances are created.
💬 Let’s Discuss!
- Have you ever checked your Compose Metrics and found a surprise “Unstable” class?
- Do you prefer using
@Immutableannotations or strictly usingPersistentList? - What’s the biggest performance bottleneck you’ve cleared by fixing stability?
Drop your thoughts in the comments below! 👇
📘 Master Your Next Technical Interview
Since Java is the foundation of Android development, mastering DSA is essential. I highly recommend “Mastering Data Structures & Algorithms in Java”. It’s a focused roadmap covering 100+ coding challenges to help you ace your technical rounds.
- E-book (Best Value! 🚀): $1.99 on Google Play
- Kindle Edition: $3.49 on Amazon
- Also available in Paperback & Hardcover.

Comments
Post a Comment