ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-06-25Advanced

Every Favorite Tap Was Redrawing Every Visible Wallpaper — Stopping Needless Recomposition in Compose, Measured

A record of fixing a wallpaper grid where toggling a single favorite recomposed every visible thumbnail. Covers turning on Composition Tracing to measure first, tracking the unstable parameter down, stabilizing the data model, handling lambdas and derived state, and comparing recomposition counts before and after from an indie developer's view.

Android18Jetpack Compose3RecompositionPerformance3Composition Tracing

Premium Article

While reworking the favorites screen of one of my wallpaper apps, I noticed that every time I tapped the heart on a thumbnail, the dozen-or-so thumbnails on screen flickered for a frame. Only the tapped tile should change, yet every visible cell in the viewport looked like it was being redrawn.

During a scroll, that flicker turns into a stutter. On my own Pixel it stayed barely smooth, but on the older phone a friend lends me it clearly hitched. When you maintain the same app for years as an indie developer, you get used to the speed of your own device and stop noticing this kind of regression. I caught it while scrolling and mashing the favorite button on a slow device. The cause was that Jetpack Compose's recomposition scope had quietly grown too wide, so I started by turning "how many times is this redrawing?" into a number.

Don't start tuning on a guess — make recomposition counts visible

The worst thing you can do with a Compose performance problem is sprinkle remember and key around on intuition. If you change things without knowing which Composable recomposes how often, you can't tell whether it helped, and you break something else in the process.

The first step was enabling Composition Tracing. It makes the System Trace show, by name, which Composable functions composed and recomposed and how many times. The setup itself is boilerplate, so I asked the Antigravity agent to "enable composition tracing for debug builds only" and reviewed the diff it produced.

// app/build.gradle.kts
dependencies {
    // For Composition Tracing (debug is enough)
    implementation("androidx.tracing:tracing-perfetto:1.0.0")
    implementation("androidx.tracing:tracing-perfetto-binary:1.0.0")
}
 
// Tell the Compose compiler to emit composition trace markers
composeCompiler {
    // Only true for debug measurement. Never ship this in release.
    includeTraceMarkers = true
}

I captured the trace with the System Trace profiler in Android Studio, or by launching androidx.tracing.perfetto from Macrobenchmark. After launch I toggled one favorite, opened the trace in Perfetto, and counted the slices for the thumbnail Composable. That was the moment "one toggle drives recompositions equal to the visible cell count" stopped being a suspicion and became a fact.

The culprit was an unstable parameter

The classic reason recomposition spreads is that an argument passed to a Composable is judged "unstable" by the Compose compiler. Any Composable that takes even one unstable argument drops out of being skippable, and gets recomposed unconditionally whenever its parent recomposes.

My grid item was receiving this data class.

// Looks like an ordinary model, but Compose sees it as unstable
data class WallpaperItem(
    val id: String,
    val thumbnailUrl: String,
    val tags: List<String>,        // List is an interface, treated as unstable
    var isFavorite: Boolean,       // var is the clincher for instability
)

List<String> is an interface type whose implementation could be swapped, so the compiler can't assume its contents won't change. And a var property is the clincher, because there's no guarantee about when it gets reassigned. Because this one model was unstable, the whole thumbnail Composable that received it stopped being skippable, and every time the favorites Set updated and the parent recomposed, all the visible cells were dragged along.

You can confirm the stability verdict in the Compose compiler's report output.

# With compiler reports configured in build.gradle.kts
./gradlew assembleRelease \
  -Pandroidx.compose.compiler.reports.destination=build/compose_reports
 
# The generated *-classes.txt lists stable/unstable per class
# e.g. unstable class WallpaperItem

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Wire up Composition Tracing so you can read how many times each Composable recomposes per interaction as a number, not a guess
Trace why a LazyVerticalGrid redrew every visible thumbnail on each favorite toggle down to the unstable parameter, then narrow recomposition to the single tapped cell with @Immutable, stable keys, and derivedStateOf
Compare recomposition counts and scroll frame times before and after, and find a line for how much measurement to delegate to an agent versus which design decisions to keep yourself
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-06-23
Only Slow Right After Install — Cutting Android Cold-Start Time with Baseline Profiles, Measured
Why an Android app stutters on launch only right after install or update, explained through JIT and Cloud Profiles, plus a measured walkthrough of cutting cold-start time with Baseline Profiles — from building a Macrobenchmark harness to staged rollout, from an indie developer's perspective.
App Dev2026-03-15
Antigravity × Android Production Development — Building Shippable Apps with Jetpack Compose + MVI
An advanced guide to building production-quality Android apps with Antigravity. Covers MVI architecture, Jetpack Compose performance optimization, error handling strategy, testing patterns, and CI/CD — all through AI-assisted development.
App Dev2026-03-10
Android Studio × Antigravity Android Dev Guide — Accelerate Kotlin/Jetpack Compose with AI
Master Android app development by combining Android Studio with Antigravity. From Jetpack Compose UI generation to testing and Google Play deployment.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →