-1

I created a multi-select list, but the issue is it doesn't recompose right away (the checkmark doesn't show immediately, I have to scroll down then up for it to appear). Here is my current code:

Data class:

data class Item(
val obj: SomeObject? = null,
var isSelected: Boolean = false
)

ViewModel:

private val _itemsList: MutableStateFlow<MutableList<Item>> = MutableStateFlow(mutableListOf())
val itemsList = _itemsList.asStateFlow()
fun updateSelection(item: Item) {
    _itemsList.value.find {
          it.obj?.id == item.obj?.id
    }?.isSelected = item.isSelected
}

Composable functions:

                 Row(
                        modifier = modifier
                            .fillMaxWidth()
                            .clickable {
                                item.isSelected = !item.isSelected
                                onCheckClicked(item)
                            }
                    ) {
                        CheckableCard(
                            // other unrelated params,
                            isSelected = item.isSelected,
                            onCheckClicked = null
                        )
                    }
                }

@Composable
fun CheckableCard(
    // unrelated params,
    isSelected: Boolean,
    onCheckClicked: ((Boolean) -> Unit)? = null,
) {
    val checkedState by remember { mutableStateOf(isSelected) }

    Card(
        modifier = modifier
            .fillMaxWidth()
            .wrapContentHeight()
            .padding(top = 16.dp),
    ) {
        Row(
            modifier = modifier
                .padding(8.dp)
        ) {
            // unrelated content
                
            Checkbox(
                checked = checkedState,
                onCheckedChange = onCheckClicked
            )
        }
    }
}

I added the onCheckClicked in the Row instead of passing it to the CheckableCard because this way the item's isSelected gets updated correctly when checking/unchecking the item. However this isn't the case when I pass the function to CheckableCard (I'm monitoring the behaviour via log.D and when the function is passed to CheckableCard nothing gets updated)

Markus RoI
  • 31
  • 4

2 Answers2

1

A few things.

  1. Your Row with the clickable, you can use the modifier toggleable instead of clickable. With that, you can set the checkbox onCheckedChange to null.

  2. Item.isSelected is not a state. So it won't trigger a recomposition. Your updateSelection is just changing that variable and won't update the list. You can try using a MutableStateList, copy the list with the update (might be expensive), or make isSelected a mutable state (might not work for some architectures)

  3. You shouldn't have a state in your checkablecard. You are passing in isSelected. That should be set right in the Checkbox

@Composable
fun CheckableCard(
    // unrelated params,
    isSelected: Boolean,
) {
    Card(
        modifier = modifier
            .fillMaxWidth()
            .wrapContentHeight()
            .padding(top = 16.dp),
    ) {
        Row(
            modifier = modifier
                .padding(8.dp)
        ) {
            // unrelated content
                
            Checkbox(
                checked = isSelected,
                onCheckedChange = null
            )
        }
    }
}
jakepurple13
  • 144
  • 2
  • 8
1

You manage composable state wrong.

data class Item(
   val obj: SomeObject? = null,
   val isSelected: Boolean = false
)

fun updateSelection(item: Item) {
   _itemsList.value = _itemsList.value?.map {
        if (it.obj?.id == item.obj?.id)
            it.copy(isSelected = !item.isSelected)
        else
            it
    }
}

Row(
    modifier = modifier
        .fillMaxWidth()
        .clickable {
            onCheckClicked(item)
        }
) {
    CheckableCard(
        // other unrelated params,
        isSelected = item.isSelected,
    )
}

@Composable
fun CheckableCard(
    // unrelated params,
    isSelected: Boolean,
) {
    Card(
        modifier = modifier
            .fillMaxWidth()
            .wrapContentHeight()
            .padding(top = 16.dp),
    ) {
        Row(
            modifier = modifier
                .padding(8.dp)
        ) {
            // unrelated content
                
            Checkbox(
                checked = isSelected,
                onCheckedChange = {}
            )
        }
    }
}
Igor Konyukhov
  • 394
  • 4
  • 7