Sharukh Rahman
Sharukh Rahman

Reputation: 521

Jetpack Compose LazyColum state not changing when changes are made inside of containing objects

enter image description here

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

Upvotes: 2

Views: 1566

Answers (2)

Sharukh Rahman
Sharukh Rahman

Reputation: 521

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>)
            }
        }
}

Upvotes: 0

Phil Dukhov
Phil Dukhov

Reputation: 87794

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)

Upvotes: 2

Related Questions