'Jetpack Compose LazyColum state not changing when changes are made inside of containing objects
Below is a simple Cart Implementation where the value of qty should increase by 1 if a list item is clicked...
But somehow values in the ViewModel are changing, but not reflected in UI ( not recomposing with new qty value)
I think problem lies in this part :
LazyColumn {items(cartInfo.value!!.cartItems){...}
as cartItems is not directly a state value that's why not triggering the recomposition of the LazyColumn UI.
But cant solve this problem as mutableStateOf<CartInfo?> is fixed and I don't want a separate state for MutableList<CartItem>
data class User(var id: String, var name: String)
data class Product(var id: String, var name: String)
data class CartItem(var product: Product, var qty: Int)
data class CartInfo(val userid: String, val cartItems: MutableList<CartItem>)
val user = User("1", "A")
val item1 = CartItem(Product("1", "iPhone"), 1)
val item2 = CartItem(Product("2", "Xbox"), 1)
val cart = CartInfo(user.id, mutableListOf(item1, item2))
class MainViewModel : ViewModel() {
private val _cartInfo = mutableStateOf<CartInfo?>(null)
val cartInfo: State<CartInfo?> = _cartInfo
init {
_cartInfo.value = cart
}
fun increaseQty(product: Product, user: User) {
viewModelScope.launch {
var cartInfo = cartInfo.value
if (user.id == cartInfo?.userid) {
var cartItems = cartInfo.cartItems.map {
if (it.product.id == product.id) {
it.qty += 1
it
} else
it
}
_cartInfo.value = cartInfo.copy(cartItems = cartItems as MutableList<CartItem>)
}
// Log.d("debug", "viewmodel: ${cartInfo?.cartItems.toString()}")
}
}
}
class MainActivity : ComponentActivity() {
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val cartInfo = viewModel.cartInfo
Log.d("debug", cartInfo.toString())
LazyColumn {
items(cartInfo.value!!.cartItems) {
Card(modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
.clickable {
viewModel.increaseQty(it.product, user)
}) {
Row() {
Text(
text = "${it.product.name}",
modifier = Modifier.weight(1f).padding(10.dp)
)
Text(
text = "qty: ${it.qty}"
)
}
}
}
}
}
}
}
}
}
Thanks in advance
Solution 1:[1]
In this line _cartInfo.value = cartInfo you set the same object in your _cartInfo.
Mutable state cannot check if the state of your internal object has changed, it can only check if it is the same object or not.
You can make your CartInfo a data class which allows you to easily create a new object with copy. Make sure you do not use any var or modifiable collections such as MutableList to reduce the chance of error. Check out Why is immutability important in functional programming?.
data class CartInfo(val id: String, val cartItems: List<Item>)
// usage
_cartInfo.value = cartInfo.copy(cartItems = cartItems)
Solution 2:[2]
use derivedStateOf Observe changes in object properties, please try:
val cartInfo by remember{ derivedStateOf{viewModel.cartInfo!!.cartItems}}
items(cartInfo)
or Check this answer and modify your data class use copy change
Solution 3:[3]
Creating new CartItem with CartItem(it.product, qty = it.qty + 1) solved the problem:
fun increaseQty(product: Product, user: User) {
viewModelScope.launch {
var cartInfo = cartInfo.value
if (user.id == cartInfo?.userid) {
var cartItems = cartInfo.cartItems.map {
if (it.product.id == product.id) {
// old:
// it.qty += 1
// it
// changes made:
CartItem(it.product, qty = it.qty + 1)
} else
it
}
_cartInfo.value = cartInfo.copy(cartItems = cartItems as MutableList<CartItem>)
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Pylyp Dukhov |
| Solution 2 | Yshh |
| Solution 3 | Sharukh Rahman |

