Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpacklazycolumn

Single Selection - DeSelection in Lazy Column


PROBLEM ::: I want to create a lazy column where I can select or deselect only one option at a time. Right now, whenever I click on row component inside lazy column, all the rows get selected.

CODE :::

@Composable
fun LazyColumnWithSelection() {

    var isSelected by remember {
        mutableStateOf(false)
    }
    
    var selectedIndex by remember { mutableStateOf(0) }
    
    val onItemClick = { index: Int -> selectedIndex = index }

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
    ) {
        items(100) { index ->

            Row(modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onItemClick.invoke(index)
                    if (selectedIndex == index) {
                        isSelected = !isSelected
                    }
                }
                .padding(16.dp),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically) {
                Text(text = "Item $index", modifier = Modifier.padding(12.dp), color = Color.White)
                if (isSelected) {
                    Icon(imageVector = Icons.Default.Check,
                        contentDescription = "Selected",
                        tint = Color.Green,
                        modifier = Modifier.size(20.dp))
                }
            }

        }
    }
}

CURRENT RESULT :::

Before Clicking ->

Before Clicking

After Clicking ->

After Clicking

You can see all the items are getting selected but I should be able to select or deselect one item at a time not all.

I tried to use remember state for selection but I think I'm doing wrong something in the index selection or maybe if statement.


Solution

  • This should probably give you a head start.

    So we have 4 components here:

    • Data Class
    • Class state holder
    • Item Composable
    • ItemList Composable

    ItemData

      data class ItemData(
          val id : Int,
          val display: String,
          val isSelected: Boolean = false
      )
    

    State holder

    class ItemDataState {
    
        val itemDataList = mutableStateListOf(
            ItemData(1, "Item 1"),
            ItemData(2, "Item 2"),
            ItemData(3, "Item 3"),
            ItemData(4, "Item 4"),
            ItemData(5, "Item 5")
        )
    
        // were updating the entire list in a single pass using its iterator
        fun onItemSelected(selectedItemData: ItemData) {
            val iterator = itemDataList.listIterator()
    
            while (iterator.hasNext()) {
                val listItem = iterator.next()
    
                iterator.set(
                    if (listItem.id == selectedItemData.id) {
                        selectedItemData
                    } else {
                        listItem.copy(isSelected = false)
                    }
                )
            }
        }
    }
    

    Item Composable

    @Composable
    fun ItemDisplay(
        itemData: ItemData,
        onCheckChanged: (ItemData) -> Unit
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(80.dp)
                .border(BorderStroke(Dp.Hairline, Color.Gray)),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
    
            Text(text = if (itemData.isSelected) "I'm selected!" else itemData.display)
    
            Checkbox(
                checked = itemData.isSelected,
                onCheckedChange = {
                    onCheckChanged(itemData.copy(isSelected = !itemData.isSelected))
                }
            )
        }
    }
    

    Finally the ItemList (LazyColumn)

    @Composable
    fun ItemList() {
    
        val itemDataState = remember { ItemDataState() }
    
        LazyColumn {
            items(itemDataState.itemDataList, key = { it.id } ) { item ->
                ItemDisplay(
                    itemData = item,
                    onCheckChanged = itemDataState::onItemSelected
                )
            }
        }
    }
    

    All of these are copy-and-pasteable so you can run it quickly. The codes should be simple enough for you to dissect them easily and use them as a reference for your own use-case.

    Notice that we use a data class here which has an id property to be unique and we're using it as a key parameter for LazyColumn's item.

    I usually implement my UI collection components with a unique identifier to save me from potential headaches such as UI showing/removing/recycling wrong items.

    enter image description here