Проблема рекомпозиции при множественном выборе в LazyColumn
Я столкнулся с проблемой рекомпозиции при реализации множественного выбора строк в LazyColumn на Android Jetpack Compose. Рабочее решение есть - пометить класс @Stable, но оно только создаёт компромисс с компилятором.
Может у кого появятся идеи, как реализовать класс, чтобы аннотация @Stable не требовалась.
Исходные данные:
- Есть класс модели из сторонеей библиотеки (Java), которые отображаются в строке LazyColumn как элементы;
- LazyColumn должна поддерживать множественный выбор и сохранять выбранные элементы через
rememberSaveable { }
Для реализации выбора я написал класс с функцией получения ключа объекта, а ключи уже потом могут сохраняться в Bundle:
class Selector<I, K> (
private val selectable: SnapshotStateList<I>,
private val keyProvider: (I) -> (K)
)
Полный код: GitHub Gist
Состояния выбора элемента я сохраняю в map с ключом keyProvider и состоянием выбора (State):
private val _selectedStates = ArrayMap<K, WeakReference<MutableState<Boolean>>>()
val selectedState: (I) -> State<Boolean>
get() = state@{ item ->
val key = keyProvider(item)
val currentState = _selectedStates[key]?.get()
return@state if (currentState != null) {
currentState
} else {
val state = mutableStateOf(_selection.containsKey(key))
_selectedStates[key] = WeakReference(state)
state
}
}
Состояния читаю в блоке items в LazyColumn и, в зависимости от состояния, меняю фон:
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = contentPadding,
state = listState
) {
items(
items = items,
key = { item -> item.hashCode() }
) { item ->
val selectedState = selector.selectedState(item)
val isSelected = { selectedState.value }
/* Some code with isSelected() reading */
}
}
Проблема рекомпозиции возникает при изменениях contentPadding. При прокрутке TopAppBar на Scaffold меняется contentPadding и все элементы в LazyColumn подвергаются рекомпозиции, т.к. map _selectedStates нестабильна.
Рабочее решение: использовать аннотацию @Stable на классе Selector, но мне кажется, что это не очень аккуратно.
По другому: если заменить _selectedStates на SnapshotStateMap (mutableStateMapOf<>()), то ненужная рекомпозиция возникает при первой загрузке элемента:
- Загружается состояние выбора из
_selectedStates; Stateпомещается в map, что приводит к изменению_selectedStates;- Из-за изменения map, compose сообщается, что может что-то поменялось в
Stateи элемент вLazyColumnпересоздаётся.
remember { selector.selectedState } не помогает.
И ещё: если не сохранять состояния выбора, а только отдавать функцию isSelected, то происходит рекомпозиация каждого элемента, при изменении выбора лишь одного:
val isSelected: (I) -> Boolean = { item -> _selection.contains(keyProvider(item)) }
Может есть решение поинтереснее, что сохранение State выбранности элемента оставалось стабильным для compose, без аннотаций @Stable или @Immutable?
Тот же вопрос на английском: Multiple selection LazyColumn recompose issue