Кажется, все уже знают, что в каком-нибудь LazyColumn можно элементам задавать ключи и они используются, чтобы один и тот же композабл лишний раз не теребить. Выглядит примерно так:
LazyColumn {
items(items, key = { it.id }) {
ListItem(it.text)
}
}
Когда мы хотим сюда добавить какой-то хедер возникает абсолютно естественное желание по аналогии написать вот так:
LazyColumn {
item(key = { "header" }) { Header() }
items(items, key = { item -> item.id }) {
ListItem(it.text)
}
}
Собирается, запускается, работает. И потом крашится в проде. 😮
Наблюдаем за прелестями композного API. Вот функция items (упрощённая):
fun <T> LazyListScope.items(
items: List<T>,
key: ((T) -> Any)? = null,
contentType: (T) -> Any? = { null },
itemContent: @Composable LazyItemScope.(T) -> Unit
)
А вот один item:
fun item(
key: Any? = null,
contentType: Any? = null,
content: @Composable LazyItemScope.() -> Unit
)
И в том и в другом случае мы можем передать в key лямбду, потому что у ребят из гугла тут Any? в параметрах. Почему бы нет. И только в комментарии рядом с функцией осторожно написано, что key под капотом на андроиде будет сохраняться в Bundle, поэтому он должен быть или примитивным, или строкой, или Parcelable. А в случае с items - всё тоже самое актуально для key().
Соответственно тут нужно быть офигенно осторожным и помнить что писать надо так:
LazyColumn {
item(key = "header") { Header() }
items(items, key = { item -> item.id }) {
ListItem(it.text)
}
}
Если бы я писал функцию item, то я бы перегрузил её для всех сохраняемых в бандл типов, наверно. Или вообще явную Parcelable обёртку для ключа сделал, её же и в лямбде для items возвращал.
Это вот тот самый кейс за который вас по рукам на код-ревью бьют за Any? где не нужно. Подозреваю, что они таким лайфхаком решали проблему зависимости от Android SDK в foundation, но от этого не проще, честно говоря.