Search code examples
kotlinandroid-jetpack-composeandroid-jetpackandroid-jetpack-compose-text

deleting item from a mutableStateListOf it keeps the value of the deleted TextField passing this value to the other TextField below


deleting item from a mutableStateListOf it keeps the value of the deleted TextField passing this value to the other TextField below. I don't know if this is a error from Jetpack Compose.

This is a bit confusing since I deleted the first line right.

My code:

private var ids = mutableStateListOf<ShoppingCart>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent{
            i = intent.getParcelableArrayListExtra("produtos")!!
            ids=i.toMutableSet().toMutableStateList()

            ids = remember { ids }
                
            MainContent()
        }
    }

LazyColumn(
    modifier = Modifier
    .padding(top = 50.dp)
    .weight(1f)
    .border(4.dp, colorResource(id = R.color.pastel_green))
    ) {
        itemsIndexed(ids) { index, item ->

            var quantidades by rememberSaveable(stateSaver = TextFieldValue.Saver) {
                mutableStateOf(TextFieldValue(item.product_quant.toString()))
            }


            TextField(
                    value = quantidades,
                    onValueChange = {
                        quantidades = it

                        if (it.text.isNotEmpty()) {
                            item.product_quant = it.text.toInt()
                            calcular()
                        }
                    },
                    shape = RoundedCornerShape(8.dp),
                    colors = TextFieldDefaults.textFieldColors(
                            focusedIndicatorColor = Color.Transparent,
                            unfocusedIndicatorColor = Color.Transparent,
                            disabledIndicatorColor = Color.Transparent

                    ),
                    keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Number
                    ),
                    modifier = Modifier
                        .width(70.dp)
                        .height(70.dp)
                        .padding(top = 20.dp)

            )


            Button(
                    onClick = {
                        ids.removeAt(index)
                        
                    },
                    modifier = Modifier
                        .padding(top = 20.dp, end = 20.dp)
            ) {
                Icon(
                        Icons.Default.Delete,
                        stringResource(id = R.string.deletar)
                )
            }
        }

enter image description here


Solution

  • When you're using items of lazy view, it creates a scope depending on the key.

    And when you pass no key, the default value is item index.

    All remember values inside are bound to that key, so when you remove first cell, second one reuses it remember value, that's what happening in your case. You can pass some item id to prevent that.

    But if you would have many items and would like to scroll your list, you'll see that these items are not gettings saved. You could've create a mutable state list, but it'll be cleaned during screen rotation.

    I strongly recommend you out of storing state variables as global variables.

    Instead of that, all data that should be shared and which you don't wanna loose should be inside a view model. It's gonna be shared in the whole composable tree, unless you're using compose navigation. For this case check out this answer how you can share it.

    As you need to modify product_quant, I suggest you making it a mutable state, so changes will trigger recomposition.

    If you can't update ShoppingCart, you can create a ShoppingCartState wrapper, which will have a mutable state and update ShoppingCart value(if you need this):

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val produtos = intent.getParcelableArrayListExtra("produtos")!!
        setContent{
            // initializing view model with produtos
            viewModel<ScreenViewModel>(
                factory = object : ViewModelProvider.Factory {
                    override fun <T : ViewModel?> create(modelClass: Class<T>) =
                        ScreenViewModel(produtos) as T
                }
            )
            TestView()
        }
    }
    
    class ShoppingCartState(val shoppingCart: ShoppingCart) {
        private val _product_quant = mutableStateOf(shoppingCart.product_quant)
        var product_quant: Int
            get() = _product_quant.value
            set(value) {
                _product_quant.value = value
                shoppingCart.product_quant = value
            }
    }
    
    class ScreenViewModel(ids: List<ShoppingCart>) : ViewModel() {
        val shoppingCartStates = ids.map(::ShoppingCartState).toMutableStateList()
    
        fun calcular() {
            // do your calculations
        }
    }
    
    @Composable
    fun TestView() {
        // you can pass view model as a parameter, but this also
        // will return same view model across whole composable tree
        val viewModel = viewModel<ScreenViewModel>()
        LazyColumn(
            modifier = Modifier
                .padding(top = 50.dp)
                .border(4.dp, Color.Green)//colorResource(id = R.color.pastel_green))
        ) {
            itemsIndexed(viewModel.shoppingCartStates) { index, item ->
                Row {
                    TextField(
                        value = item.product_quant.toString(),
                        onValueChange = {
                            if (it.isNotEmpty()) {
                                item.product_quant = it.toInt()
                                viewModel.calcular()
                            }
                        },
                        shape = RoundedCornerShape(8.dp),
                        colors = TextFieldDefaults.textFieldColors(
                            focusedIndicatorColor = Color.Transparent,
                            unfocusedIndicatorColor = Color.Transparent,
                            disabledIndicatorColor = Color.Transparent
    
                        ),
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Number
                        ),
                        modifier = Modifier
                            .width(70.dp)
                            .height(70.dp)
                            .padding(top = 20.dp)
    
                    )
    
    
                    Button(
                        onClick = {
                            viewModel.shoppingCartStates.removeAt(index)
                        },
                        modifier = Modifier
                            .padding(top = 20.dp, end = 20.dp)
                    ) {
                        Icon(
                            Icons.Default.Delete,
                            "delete"
                            //stringResource(id = R.string.deletar)
                        )
                    }
                }
            }
        }
    }