← All shorts
22Compose 1.0+2 min read

Stable Keys in LazyColumn: Why Item Identity Matters

Give LazyColumn items a stable key or Compose loses item state and over-recomposes on every list mutation.

By default, LazyColumn identifies items by their position in the list. Delete the first message and every remaining row shifts index. Compose diffs that as "every row changed," so remembered state like an expanded row or a text field's contents attaches to the wrong item.

#The old way

@Composable
fun MessageList(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageRow(message)
        }
    }
}

#The new way

data class Message(val id: Long, val author: String, val body: String)

@Composable
fun MessageList(messages: List<Message>) {
    LazyColumn {
        items(
            items = messages,
            key = { it.id }
        ) { message ->
            MessageRow(message)
        }
    }
}

#Why it matters

  • Only the rows that actually changed recompose. Compose tracks items by key across insertions, removals, and reorders.
  • rememberSaveable and per-item remember state survive reordering, because they're attached to the id rather than the slot position.
  • Item-move animations (Modifier.animateItem) read the key to know which item moved where.

#Gotcha

The key must be unique within the list and stable across recompositions. Duplicate keys throw IllegalArgumentException: Key ... was already used.

Deriving the key from content that can change defeats the purpose. Take message.body.hashCode(). Edit two messages into the same text and they collide, and you're back to the same crash.

androidcomposelists

Related shorts