본 글에서는 동일한 Key를 사용시 크래시가 발생하는 내부 코드 통해 이유를 살펴보겠습니다.
Android Developers의 Compose 가이드에서 설명하는 것처럼 LazyColumn/Row에서의 Key는 고유한 값을 사용하도록 안내하고 있습니다.
Key가 동일하면 크래시가 발생하는 것이 이유입니다.
java.lang.IllegalArgumentException: Key "1" was already used. If you are using LazyColumn/Row please make sure you provide a unique key for each item.
Key로 “1”을 반환하도록 했을 때의 결과
LayoutNodeSubcompositionsState#subcompose로 전달된 파라미터 slotId
로 처리 시, root.foldedChildren
에서 node로 위치를 찾은 결과(=itemIndex)가 현재 위치(=currentIndex)보다 이전의 위치일 때 해당 크래시가 발생합니다.
internal class LayoutNodeSubcompositionsState(
private val root: LayoutNode,
...
) : ComposeNodeLifecycleCallback {
// this map contains active slotIds (without precomposed or reusable nodes)
private val slotIdToNode = hashMapOf<Any?, LayoutNode>()
...
fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> {
...
val node = slotIdToNode.getOrPut(slotId) { ... }
if (root.foldedChildren.getOrNull(currentIndex) !== node) {
// the node has a new index in the list
val itemIndex = root.foldedChildren.indexOf(node)
requirePrecondition(itemIndex >= currentIndex) {
"Key \"$slotId\" was already used. If you are using LazyColumn/Row please make " +
"sure you provide a unique key for each item."
}
if (currentIndex != itemIndex) {
move(itemIndex, currentIndex)
}
}
currentIndex++
subcompose(node, slotId, content)
return ...
}
}
소스 출처 : https://github.com/androidx/androidx/blob/androidx-main/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt#L480-L483
LayoutNodeSubcompositionsState#subcompose를 호출하는 곳은 LazyLayoutMeasureScopeImpl이며, item의 key
값이 subcompose 함수의 slotId 파라미터로 전달됩니다.
internal class LazyLayoutMeasureScopeImpl
internal constructor(
private val itemContentFactory: LazyLayoutItemContentFactory,
private val subcomposeMeasureScope: SubcomposeMeasureScope
) : LazyLayoutMeasureScope, MeasureScope by subcomposeMeasureScope {
...
override fun measure(index: Int, constraints: Constraints): List<Placeable> {
val cachedPlaceable = placeablesCache[index]
return if (cachedPlaceable != null) {
cachedPlaceable
} else {
val key = itemProvider.getKey(index)
val contentType = itemProvider.getContentType(index)
val itemContent = itemContentFactory.getContent(index, key, contentType)
val measurables = subcomposeMeasureScope.subcompose(key, itemContent)
...
}
}
소스 출처 : https://github.com/androidx/androidx/blob/androidx-main/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt#L116-L128
LayoutNodeSubcompositionsState#subcompose 내부에서 크래시가 나는 케이스를 간단하게 정리하면 아래와 같습니다.
currentIndex
를 1 증가root.foldedChildren
에서 위치를 검색하면 1번에서 추가된 Node의 위치가 반환(=itemIndex)requirePrecondition(itemIndex >= currentIndex)
에 의해서 itemIndex위치가 currentIndex보다 값이 작으므로 Crash가 발생
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024