Jetpack Compose Performance: Best Practices

Jetpack Compose Best Practices

Keep your UI fast, smooth, and recomposition-free! Learn the official optimization patterns every Android dev should know.

What you’ll learn

  • Optimize recompositions with remember & derivedStateOf
  • Use stable keys for lazy lists
  • Defer state reads & move heavy work outside composition
  • Avoid backwards writes & redundant recompositions
  • Hoist and manage state efficiently

Cache Expensive Work

Use remember to store heavy computations and avoid repeating them on every recomposition.

val sorted = remember(contacts, comparator) {
    contacts.sortedWith(comparator)
}
LazyColumn { items(sorted) { ContactItem(it) } }

💡 Tip: Run once, reuse often.

Stable Keys in Lazy Lists

Provide stable key values to prevent unnecessary recompositions when list items move.

LazyColumn {
    items(notes, key = { it.id }) { note ->
        NoteRow(note)
    }
}

💡 Tip: Stable keys = smoother scrolling.

Limit Recomposition

Use derivedStateOf to recompose only when truly needed.

val showButton by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
AnimatedVisibility(showButton) { ScrollToTopButton() }

💡 Tip: Compose smart, not often.

Defer State Reads

Read fast-changing state only where it’s used, not at the parent level.

Title(snack) { scroll.value }

@Composable
fun Title(snack: Snack, scrollProvider: () -> Int) {
    val offset = with(LocalDensity.current) { scrollProvider().toDp() }
    Column(Modifier.offset(y = offset)) { Text(snack.name) }
}

💡 Tip: Read only where needed.

Use Lambda Modifiers

Lambda-based modifiers like offset {} or drawBehind {} skip the composition phase and improve frame time.

Box(
    Modifier.drawBehind {
        drawRect(color)
    }
)

💡 Tip: Moves state reads to layout/draw phase.

Avoid Backwards Writes

Never modify state after reading it in the same composition - it causes infinite recompositions.

var count by remember { mutableIntStateOf(0) }

Button(onClick = { count++ }) { 
    Text("Count: $count") 
}
// ❌ count++ // Never write after a read!

💡 Tip: Always update state in event callbacks.

Hoist State Up

Keep state outside child composables so they remain stateless and reusable.

var name by rememberSaveable { mutableStateOf("") }
NameInputField(name, { name = it })

💡 Tip: Children should display, not own, state.

Avoid Heavy Work in Composition

Don’t call I/O, network, or database operations in composables. Use a ViewModel and collect with collectAsState().

val uiState by viewModel.state.collectAsState()
Text(uiState.title)

💡 Tip: Compose = UI, not business logic.

Final Checklist

  • ✅ Cache heavy work with remember
  • ✅ Add stable keys for Lazy lists
  • ✅ Use derivedStateOf wisely
  • ✅ Defer reads using lambda modifiers
  • ✅ Avoid backwards writes
  • ✅ Hoist state up
  • ✅ Offload logic to ViewModel

Thank You - Follow Boltuix

Daily Jetpack Compose insights - layouts, shadows, adaptive UIs & performance tips.

#Android #JetpackCompose #Kotlin #ComposePerformance #AndroidDevelopers

Post a Comment

Previous Post Next Post