Compose Metrics REVEALED 🔥: Make 80% of Your Composable Functions SKIPPABLE
The Hidden Truth About Your Compose UI Performance
You’ve written beautiful Jetpack Compose UIs. They look smooth in preview. But on real devices with real data? Jank.
Why? Compose is restarting composables it could skip. And you have zero visibility into what’s happening.
Until now.
Compose 1.2+ ships with compiler metrics that reveal exactly how Compose sees your code:
"skippableComposables": 64,
"restartableComposables": 76 // 12 functions = performance leaks!This single JSON file tells you which 12 functions are killing your 60fps.
What Do “Restartable” vs “Skippable” Actually Mean?
Restartable = Recomposition Boundary ✅
restartable fun MyComposable(show: ShowUiModel)- Good: Compose can restart just this function when state changes
- Default behavior: All
@Composablefunctions are restartable - Think: “This is a checkpoint where recomposition starts”
Skippable = Performance Rocket Fuel 🚀
restartable skippable fun MyComposable(show: ShowUiModel)- Elite: Compose can skip the entire function if parameters didn’t change
- Game-changer: Top-level composables skip = entire UI subtrees skip
- The goal: Make your hot-path composables restartable + skippable
Real example from Chris Banes’ Tivi app:
❌ restartable fun AirsInfoPanel(unstable show: TiviShow)
✅ restartable skippable fun AirsInfoPanel(stable show: ShowUiModel)Enable Metrics (2 Minutes Setup)
Add to your root build.gradle:
subprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
if (project.findProperty("enableComposeMetrics") == "true") {
freeCompilerArgs += [
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
"${project.buildDir.absolutePath}/compose_metrics",
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
"${project.buildDir.absolutePath}/compose_metrics"
]
}
}
}
}Run (release build only!):
./gradlew assembleRelease -Pmyapp:enableComposeMetrics=true --rerun-tasksOutput: app/build/compose_metrics/ contains:
module.json← Overall statscomposables.csv← Filter "Not skippable"composables.txt← Deep dive
The Most Common Problem: “Unstable” Parameters
Spot this pattern:
restartable fun ShowDetails(unstable show: TiviShow, ...)Why unstable?
- Data class lives in non-Compose module
- External library types (
ViewModel, date classes) - Lists of stable types (Compose compiler limitation)
Metrics gold nugget:
"knownStableArguments": 890, // Great!
"knownUnstableArguments": 30, // Fix these!3 Fixes (From Easiest to Hardest)
1. UI Model Classes (Recommended)
// ❌ Data layer (unstable in Compose metrics)
data class TiviShow(val name: String, val genres: List<Genre>)// ✅ UI layer (next to your composables)
@Immutable
data class ShowUiModel(
val name: String,
val genres: List<Genre>
)
ViewModel maps:
val uiState = showList.map { show ->
ShowUiModel(
name = show.name,
genres = show.genres
)
}2. @Stable/@Immutable Annotations
@Immutable // No mutable properties
@Stable // Mutable but notifies Compose
data class ShowUiModel(...)3. Parameter-Level @Stable (Escape hatch)
@Composable
fun AirsInfoPanel(
@Stable show: TiviShow, // Force stability
modifier: Modifier = Modifier
)Advanced: @NonRestartableComposable
For tiny forwarding functions:
@Composable
@NonRestartableComposable // Skip restart machinery
private fun Forwarder(show: ShowUiModel) {
AirsInfoPanel(show)
}Use when: No state reads, just parameter forwarding.
Gotchas You Must Know
Debug vs Release Builds
❌ Debug: modifier = @dynamic LiveLiterals$...
✅ Release: modifier = @static CompanionAlways test release builds — Live Literals fake dynamic params.
@dynamic Default Parameters
// These are OK (theme changes are rare)
backgroundColor: Color = MaterialTheme.colors.primarySurface // @dynamic// These should be @static
elevation: Dp = AppBarDefaults.TopAppBarElevation // Bug?
Your Action Plan (Do This Today)
1. [ ] Run `./gradlew assembleRelease -PenableComposeMetrics=true`
2. [ ] Open app/build/compose_metrics/module.json
3. [ ] Check: skippableComposables / totalComposables > 80%?
4. [ ] Open composables.csv → Filter "Not skippable"
5. [ ] Fix top 3 functions with UI models + @Immutable
6. [ ] Rebuild → Verify metrics improve
7. [ ] Test on real device with JankStatsPro tip: Start with your largest screen (most composables).
💬 What’s your skippable %? Drop it in comments + subscribe for more Compose deep dives!
EmailId: vikasacsoni9211@gmail.com
LinkedIn: https://www.linkedin.com/in/vikas-soni-052013160/
Happy Learning ❤️
.png)
Comments
Post a Comment