Single Selection - DeSelection in Lazy Column
Asked Answered
F

2

6

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.

Fixate answered 2/11, 2022 at 21:58 Comment(3)
Thats because you are checking the "isSelected" value in order to display / hide the icon. I think it should be if (selectedIndex == index) instead of if (isSelected). Do you need "isSelected" at all?Convexity
@Convexity if i do if (selectedIndex == index) , it going to set index 0 as default option and also there will be no option to de-select the selected row component . That's why I have to use if(isSelected). But you are also right i have to use if (selectedIndex == index) outside the clickable to get the updated selectedIndex value.Fixate
Please check this post #74151892Vachel
V
7

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

Vachel answered 3/11, 2022 at 7:3 Comment(2)
thank you so much for this briefed anwser. I modified the code a bit and able to select and de-select the component but issue is now it is performing as multi-selection.Fixate
i think i have to update the hide/show state in the viewModel iteselfFixate
H
0

Remember index instead of Boolean (isSelected).

Hobby answered 3/11, 2022 at 10:33 Comment(1)
with remember index i think i will not be able to hide the icon.Fixate

© 2022 - 2024 — McMap. All rights reserved.