Составить: LazyColumn перекомпоновывает все элементы при обновлении одного элемента

Я пытаюсь показать список заказов в списке с помощью LazyColumn. Вот код:

      @Composable
private fun MyOrders(
    orders: List<Order>?,
    onClick: (String, OrderStatus) -> Unit
) {
    orders?.let {
        LazyColumn {
            items(
                items = it,
                key = { it.id }
            ) {
                OrderDetails(it, onClick)
            }
        }
    }
}

@Composable
private fun OrderDetails(
    order: Order,
    onClick: (String, OrderStatus) -> Unit
) {
    println("Composing Order Item")
    // Item Code Here
}

Вот способ, который я называю компонуемым:

      orderVm.fetchOrders()
val state by orderVm.state.collectAsState(OrderState.Empty)

if (state.orders.isNotEmpty()) {
    MyOrders(state.orders) {
        // Handle status change click listener
    }
}

Я получаю все свои заказы и показываю в LazyColumn. Однако при обновлении одного заказа вся LazyColumn подвергается повторной компоновке. Вот моя ViewModel выглядит так:

      class OrderViewModel(
    fetchrderUseCase: FetechOrdersUseCase,
    updateStatusUseCase: UpdateorderUseCase
) {

    val state = MutableStateFlow(OrderState.Empty)

    fun fetchOrders() {
        fetchrderUseCase().collect {
            state.value = state.value.copy(orders = it.data)
        }
    }

    fun updateStatus(newStatus: OrderStatus) {
        updateStatusUseCase(newStatus).collect {
            val oldOrders = status.value.orders
            status.value = status.value.copy(orders = finalizeOrders(oldOrders))
        }
    }
}

Обратите внимание finalizeOrders() выполняет некоторые манипуляции со списком на основе orderId для обновления одного заказа обновленным.

Вот как выглядит мое состояние:

      data class OrderState(
    val orders: List<Order> = listOf(),
    val isLoading: Boolean = false,
    val error: String = ""
) {
    companion object {
        val Empty = FetchOrdersState()
    }
}

Если у меня в БД 10 заказов и я обновляю один статус (скажем 5-й), то OrderDetailsполучает вызов 20 раз. Не знаю почему. Caan Я оптимизирую его, чтобы убедиться, что будет перекомпонован только 5-й проиндексированный элемент, а OrderDetals вызывается только с новым заказом.

4 ответа

Это Orderкласс стабильный? Если нет, то это может быть причиной того, что все элементы перекомпонованы:

Compose пропускает перекомпоновку объединяемого объекта, если все входные данные стабильны и не изменились. Для сравнения используется метод equals

В этом разделе документации по составлению объясняется, что такое типы и как пропустить перекомпоновку.

Примечание : если вы прокрутите список, все невидимые элементы будут уничтожены. Это означает, что если вы прокрутите назад, они будут воссозданы, а не перекомпонованы (вы не можете пропустить воссоздание, даже если ввод stable).

Попробуйте использовать инструмент Rebugger , чтобы понять, почему ваше представление было перекомпоновано:

      @Composable
private fun OrderDetails(
    order: Order,
    onClick: (String, OrderStatus) -> Unit
) {
    Rebugger(mapOf("order" to order, "onClick" to onClick))
    ...
}

Дело было вonClickв моем случае. Compose решил, что onClick является изменяемым, поскольку содержит ссылку на изменяемый объект.

Это может произойти из-за использования List вместо SnaphshotStateList, лямбды viewModel не стабильной или самой функции.

Для части ViewModel вы можете вместо вызова

viewModel.updateStatus или viewMode::updateStatus

возможно, вам придется позвонить

      val onClick = remember {
    { orderStatus: OrderStatus ->
        viewModel.updateOrderStatus(orderStatus)
    }
}

В этом ответе объяснены возможные проблемы и способы их решения.

https://stackoverflow.com/a/74700668/5457853

Blockquote Если у меня в базе данных 10 заказов и я обновляю статус одного из них (скажем, 5-го элемента), то OrderDetails вызывается 20 раз. Не знаю почему. Я оптимизирую его, чтобы быть уверенным, что будет перекомпонован только пятый индексированный элемент, а OrderDetals будет вызываться только с новым заказом.

если вы вызываете составной объект, вы регистрируете сборщик потока при каждой рекомпозиции, и когда они собираются, они заменяют значение состояния и вызывают другую рекомпозицию и так далее.

ПереместитеorderVm.fetchOrders()вызов ViewModelinit(){}блок, поэтому он будет зарегистрирован только один раз.

Если я прав, это портит список...

      orderVm.fetchOrders()
val state by orderVm.state.collectAsState(OrderState.Empty)

if (state.orders.isNotEmpty()) {
    MyOrders(state.orders) {
        // Handle status change click listener
    }
}

Кстати, в идеале собирать поток внутриviewModelScope, поэтому он будет отменен при очистке модели представления хоста, что позволяет избежать утечек памяти.

Надеюсь, поможет...

Другие вопросы по тегам