Проблема рекомпозиции при множественном выборе в LazyColumn

Я столкнулся с проблемой рекомпозиции при реализации множественного выбора строк в LazyColumn на Android Jetpack Compose. Рабочее решение есть - пометить класс @Stable, но оно только создаёт компромисс с компилятором.

Может у кого появятся идеи, как реализовать класс, чтобы аннотация @Stable не требовалась.


Исходные данные:

  1. Есть класс модели из сторонеей библиотеки (Java), которые отображаются в строке LazyColumn как элементы;
  2. 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<>()), то ненужная рекомпозиция возникает при первой загрузке элемента:

  1. Загружается состояние выбора из _selectedStates;
  2. State помещается в map, что приводит к изменению _selectedStates;
  3. Из-за изменения 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


Ответы (0 шт):