Search code examples
androidkotlinandroid-jetpack-composelazycolumncompose-recomposition

Jetpack Compose Lazy Column single selection


I'm getting data from the server and displaying it in the list, each item can be selected with one click to display the button, but I cannot close it, I can only open it.

This is item of list class

data class Task(
    val deviceName: String,
    val deviceId: String,
    var selected :Boolean= Boolean,
)

this is data class

data class TaskStatus(
    val taskList: SnapshotStateList<Task>  = SnapshotStateList(),
    val selectedNumber: Int = -1,
)

My ViewModel

private val _status = MutableStateFlow(TaskStatus())
val status = _status.asStateFlow()

fun getList(){
  ...
  for(item in result){
    _status.value.taskList.add(task)
  }
}
fun selectTask(task: Task) {
  val list = _status.value.taskList
  val selectNumber = _status.value.selectedNumber
  val newSelectNumber = list.indexOf(task)
    if (newSelectNumber != selectNumber) {
      if (selectNumber != -1) {
        list[selectNumber].selected.value = false
      }
    }
  task.selected.value = !task.selected.value
  _status.update { it.copy(selectedNumber = newSelectNumber) }
}

My LazyColumn

...
LazyColumn(
  modifier = Modifier
    .fillMaxWidth()
    .weight(1F),
  verticalArrangement = Arrangement.spacedBy(11.dp), contentPadding = PaddingValues(16.dp)
) {
  items(
    taskStatus.taskList,
    key = { it.deviceId }) { task ->
      Item(task)
    }
}

@Compose
fun Item(task:Task){
  Column(){
    Text(text = task.name)
    Text(text = task.deviceId)
    if(task.selected){
      Botton()
    }
  }
}

I can only show but not hide the button

Thank you in advance.


Solution

  • I can't compile your code directly so I tried to make my own implementation. I added a callback which will be triggered from your Task Item

    Your TaskList composable

    @Composable
    fun TaskList(
        taskList: SnapshotStateList<Task>,
        onSelected: (Task) -> Unit
    ) {
        LazyColumn(
            modifier = Modifier
                .fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(11.dp), contentPadding = PaddingValues(16.dp)
        ) {
            items(
                taskList,
                key = { it.deviceId }) { task ->
                Item(task) {
                    onSelected(it)
                }
            }
        }
    }
    

    Your TaskItem Composable

    @Composable
    fun Item(
        task:Task,
        onSelected: (Task) -> Unit
    ){
        Column(
            modifier = Modifier.clickable {
                onSelected(task) // selection callback
            }
        ){
            Text(text = task.deviceName)
            Text(text = task.deviceId)
            if(task.selected) {
    
                Box(modifier = Modifier
                    .fillMaxWidth()
                    .height(100.dp)
                    .background(Color.Red)) {
                }
            }
        }
    }
    

    And I mocked a VieModel

    class TaskStateHolder {
        private val _status = MutableStateFlow( TaskStatus (
            taskList = mutableStateListOf(
                Task(
                    deviceName = "Device 1",
                    deviceId = "Device 1 ID"
                ),
                Task(
                    deviceName = "Device 2",
                    deviceId = "Device 2 ID"
                ),
                Task(
                    deviceName = "Device 3",
                    deviceId = "Device 3 ID"
                ),
                Task(
                    deviceName = "Device 4",
                    deviceId = "Device 4 ID"
                ),
            )
        ))
    
        val status = _status.asStateFlow()
    
        fun selectTask(task: Task) {
            _status.update {
                val list = it.taskList
                val newSelectNumber = list.indexOf(task)
                val iterator = list.listIterator()
                while (iterator.hasNext()) {
                    val obj = iterator.next()
                    if (task.deviceId == obj.deviceId) {
                        iterator.set(task.copy(selected = true))
                    } else {
                        iterator.set(obj.copy(selected = false))
                    }
                }
    
                it.copy(selectedNumber = newSelectNumber)
            }
        }
    }
    

    I modified your selectedTask function, executing _status flow udpates in a single pass using the list's iterator.

    Usage somewhere outside (e.g "TaskScreen")

    val tasks by stateHolder.status.collectAsState()
        Column {
            TaskList(tasks.taskList) {
                stateHolder.selectTask(it)
            }
        }
    

    enter image description here