Search code examples
androidkotlinstateandroid-jetpackandroid-jetpack-compose

State hoisting for each item in LazyColumn


I've gone through this codelab. In step number 7, when clicking on single row's text it's changing its color, but function will not keep track of it, meaning it will disappear after re-composition.

I want list to remember color of single item thus I've move state hoisting to the NameList function level. Unfortunately it's not working.

Where's the bug?

    @Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {

    LazyColumn(modifier = modifier) {
        items(items = names) { name, ->
            val isSelected  = remember { mutableStateOf(false)}
            Greeting(name = name,isSelected.value){ newSelected -> isSelected.value = newSelected}
            Divider(color = Color.Black)
        }
    }
}

@Composable
fun Greeting(name: String,isSelected : Boolean, updateSelected : (Boolean) -> Unit) {

    val backgroundColor by animateColorAsState(if (isSelected) Color.Red else Color.Transparent)
    Text(
        modifier = Modifier
            .padding(24.dp)
            .background(color = backgroundColor)
            .clickable(onClick = { updateSelected(!isSelected)}),
        text = "Hello $name",

        )
}

Solution

  • You should hoist your selection state to the caller of NameList function.

    @Composable
    fun MyScreen() {
        // Fake list of names
        val namesList = (1..100).map { "Item $it" }
        // Here, we're keeping the selected positions. 
        // At the beginning, all names are not selected.
        val selection = remember {
            mutableStateListOf(*namesList.map { false }.toTypedArray())
        }
        
        NameList(
            // list of names
            names = namesList, 
            // list of selected items
            selectedItems = selection,
            // this function will update the list above
            onSelected = { index, selected -> selection[index] = selected },
            // just to occupy the whole screen 
            modifier = Modifier.fillMaxSize()
        )
    }
    

    Then, your NameList will look like this:

    @Composable
    fun NameList(
        names: List<String>,
        selectedItems: List<Boolean>,
        onSelected: (index: Int, selected: Boolean) -> Unit,
        modifier: Modifier = Modifier
    ) {
        LazyColumn(modifier = modifier) {
            itemsIndexed(items = names) { index, name ->
                Greeting(
                    name = name, 
                    isSelected = selectedItems[index],
                    updateSelected = { onSelected(index, it) }
                )
                Divider(color = Color.Black)
            }
        }
    }
    

    Nothing changes on Greeting function.

    Here is the result:

    enter image description here