0

I have nested column, when I click add button the goal is add another text field and when I click delete button (which still hidden because first index) the goal is remove its text field. It seems doesn't recompose but the list size is changed.

I have tried using LazyColumn and foreach inside leads to force close, still no luck. Any help appreciated, thank you!

Preview of the screen

My current code :

@Composable
fun ProblemScreen() {
    val list = remember {
        mutableStateListOf<MutableList<String>>()
    }

    LaunchedEffect(key1 = Unit, block = {
        repeat(3) {
            val listDesc = mutableListOf<String>()
            repeat(1) {
                listDesc.add("")
            }
            list.add(listDesc)
        }
    })

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.background)
    ) {
        list.forEachIndexed { indexParent, parent ->
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 8.dp, horizontal = 16.dp)
            ) {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Text(text = "Parent ${indexParent + 1}", fontSize = 18.sp)
                    Spacer(modifier = Modifier.weight(1f))
                    Button(onClick = {
                        parent.add("")
                        println("PARENT SIZE : ${parent.size}")
                    }) {
                        Icon(imageVector = Icons.Rounded.Add, contentDescription = "Add")
                    }
                }
                parent.forEachIndexed { indexChild, child ->
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(top = 16.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        TextField(
                            value = "",
                            onValueChange = {

                            },
                            colors = TextFieldDefaults.textFieldColors(),
                            maxLines = 1,
                            modifier = Modifier.weight(1f)
                        )
                        Spacer(modifier = Modifier.width(16.dp))
                        Button(
                            onClick = {
                                parent.removeAt(indexChild)
                            },
                            modifier = Modifier.alpha(if (indexChild != 0) 1f else 0f)
                        ) {
                            Icon(
                                imageVector = Icons.Rounded.Delete,
                                contentDescription = "Delete"
                            )
                        }
                    }
                }
            }
        }
    }
}
Erwin Kurniawan A
  • 958
  • 2
  • 9
  • 17
  • Using mutable objects such as ArrayList or mutableListOf() as state in Compose causes your users to see incorrect or stale data in your app. Mutable objects that are not observable, such as ArrayList or a mutable data class, are not observable by Compose and don't trigger a recomposition when they change. Instead of using non-observable mutable objects, the recommendation is to use an observable data holder such as State> and the immutable listOf(). – bylazy Feb 01 '23 at 13:27
  • Can you provide an example for my case? – Erwin Kurniawan A Feb 01 '23 at 14:08

1 Answers1

0

As said in docs, mutable objects that are not observable, such as mutableListOf(), are not observable by Compose and don't trigger a recomposition.

So instead of

val list = remember {
    mutableStateListOf<MutableList<String>>()
}

Use:

val list = remember {
    mutableStateListOf<List<String>>()
}

And when you need to update the List, create a new one:

//parent.add("")
list[indexParent] = parent + ""
bylazy
  • 1,055
  • 1
  • 5
  • 16
  • Thank you! Now I have understand how I update the list by creating a new one. Can i ask how i assign text field value and onValueChange for the child? It seem doesn't change the value. – Erwin Kurniawan A Feb 02 '23 at 02:24
  • @Erwin Kurniawan A Same way - new list with replaced item, like `newList = oldList.toMutableList().apply{this[indexChild] = newValie}.toList()` – bylazy Feb 02 '23 at 06:27
  • Thank you it works well. Somehow it doesn't change if I changed the List using my own data class, e.g `data class(val desc: String)` , any idea why? – Erwin Kurniawan A Feb 02 '23 at 07:49
  • Should work fine, can't say why it doesn't with given description – bylazy Feb 02 '23 at 07:59
  • I tried with `data class Item(var desc: String)`. when I type, it doesn't change item.desc value. I have used `list[indexParent] = parent.toMutableList().apply { this[indexChild].desc = it }.toList()` and the text field still empty somehow, when I print, it only change 1 character. – Erwin Kurniawan A Feb 02 '23 at 08:05