Reputation: 19492
I am trying to show a list of Orders in a list using LazyColumn. Here is the code:
@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
}
Here is the way, I call the composable:
orderVm.fetchOrders()
val state by orderVm.state.collectAsState(OrderState.Empty)
if (state.orders.isNotEmpty()) {
MyOrders(state.orders) {
// Handle status change click listener
}
}
I fetch all my orders and show in the LazyColumn. However, when a single order is updated, the entire LazyColumn gets rrecomposed. Here is my ViewModel looks like:
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))
}
}
}
NOTE: The finalizeOrders()
does some list manipulation based on orderId to update one order with the updated one.
This is how my state looks like:
data class OrderState(
val orders: List<Order> = listOf(),
val isLoading: Boolean = false,
val error: String = ""
) {
companion object {
val Empty = FetchOrdersState()
}
}
If I have 10 orders in my DB and I update one's status (let's say 5th item), then OrderDetails
gets called for 20 times. Not sure why. Caan I optimize it to make sure only the 5th indexed item will be recomposed and the OrderDetals
gets called only with the new order.
Upvotes: 7
Views: 4131
Reputation: 66674
This can happen due to using a List instead of SnaphshotStateList, viewModel lambda not being stable or function itself not being stable.
For the ViewModel part you can instead of calling
viewModel.updateStatus or viewMode::updateStatus
you might need to call
val onClick = remember {
{ orderStatus: OrderStatus ->
viewModel.updateOrderStatus(orderStatus)
}
}
In this answer explained possible issues and how to solve them.
https://stackoverflow.com/a/74700668/5457853
Upvotes: 0
Reputation: 664
Try to use Rebugger tool to understand why your view was recomposed:
@Composable
private fun OrderDetails(
order: Order,
onClick: (String, OrderStatus) -> Unit
) {
Rebugger(mapOf("order" to order, "onClick" to onClick))
...
}
The issue was in onClick
in my case. Compose decided, that onClick is mutable, because it contains link to mutable object.
Upvotes: 1
Reputation: 143
Blockquote If I have 10 orders in my DB and I update one's status (let's say 5th item), then OrderDetails gets called for 20 times. Not sure why. Caan I optimize it to make sure only the 5th indexed item will be recomposed and the OrderDetals gets called only with the new order.
if you are calling orderVm.fetchOrders()
in a composable, you are registering a flow collector at every recomposition, and, when they collect, they replace the state value and call another recomposition and so on.
Move the orderVm.fetchOrders()
call to the ViewModel init(){}
block, so, it will be registered only once.
If i am right, this is messing with the list...
orderVm.fetchOrders()
val state by orderVm.state.collectAsState(OrderState.Empty)
if (state.orders.isNotEmpty()) {
MyOrders(state.orders) {
// Handle status change click listener
}
}
By the way, ideally, collect the flow inside a viewModelScope
, so, it will be cancelled when the hosting viewmodel is cleared, avoiding memory leaks.
Hope it helps...
Upvotes: 0
Reputation: 1486
Is the Order
classs stable? If not it could be the reason why all the items get recomposed:
Compose skips the recomposition of a composable if all the inputs are stable and haven't changed. The comparison uses the equals method
This section in the compose's doc explains what are stable
types and how to skip recomposition.
Note: If you scroll a lazy list, all invisible items will be destroyed. That means if you scroll back they will be recreated not recomposed (you can't skip recreation even if the input is stable
).
Upvotes: 2