Reputation: 21
So I have this code:
@Composable
fun Botones(strings: MutableMap<String, Color>) {
Column{
strings.forEach { color ->
Button(
onClick = {
// Update the button color
if (color.value == Color.Red) {
color to Color.Blue
} else if (colo.value == Color.Blue) {
color to Color.Green
} else {
color to Color.Red
}
},
colors = ButtonDefaults.buttonColors(backgroundColor = color.value)
) {
// Place the string in the buton, set font color to white
Text(colorsito.key, color = Color.White)
}
}
}
}
Initially, all colors should be red, and every time a button is clicked, it should change its color, while the other buttons should remain the same. I don't necesarely need to use a mutable map, what I want is, for every string, make a red button, that when clicked, changes it color depending on which color it had before. Problem is I can't get it to work because apparently it doesn't recompose. I tried using remember, but it doesn't detect when a value of a mutable map changes, and I sincerely don't know how to get it to work. Help would be much appreciated, I've been googlin' for a while with 0 results, and not even chatGPT was able to get it to work correctly.
EDIT: So magically Copilot suggested this, and it works, but I would very much appreciate if someone could explain why:
@Composable
fun MyComposable(items: List<String>) {
var itemsio by remember { mutableStateOf(items) }
LazyColumn {
items(itemsio.size) { index ->
var item = itemsio[index]
var color by remember { mutableStateOf(Color.Red) }
Text(item, modifier = Modifier.background(color).clickable(
interactionSource = remember {MutableInteractionSource()},
indication = null
) {
if (color == Color.Red) {
color = Color.Blue
} else if (color == Color.Blue) {
color = Color.Green
} else {
color = Color.Red
}
}
)
}
}
interactionSource and indication makes it work? but why
Upvotes: 2
Views: 8504
Reputation: 66599
It looks like CoPilot or chatGPT need some time to take our jobs 😁
Code suggested by CoPilot actually doesn't work, when you scroll up or down it resets back to Red because of how LazyList
works.
When you scroll an item out of Viewport it exits composition, when that item is visible again it enters composition with block inside remember is calculated with default value.
I made a small cosmetic change to display why that code doesn't work also it contains redundant code either.
@Composable
fun MyComposable(items: List<String>) {
// CoPilot why var and why do you need this list here if it's a param? And if list changes it won't be recomposed with new list either
var itemsio by remember { mutableStateOf(items) }
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(itemsio.size) { index ->
var item = itemsio[index]
var color by remember { mutableStateOf(Color.Red) }
Text(item,
modifier = Modifier
.background(color)
.fillMaxWidth()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
if (color == Color.Red) {
color = Color.Blue
} else if (color == Color.Blue) {
color = Color.Green
} else {
color = Color.Red
}
}
.padding(10.dp)
)
}
}
}
This can be achieved in various ways, i will post only 2.
@Composable
fun MyComposable(items: List<String>) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(items) { item ->
var colorId by rememberSaveable { mutableStateOf(0) }
val color = when (colorId) {
1 -> {
Color.Blue
}
2 -> {
Color.Green
}
else -> {
Color.Red
}
}
Text(item,
modifier = Modifier
.background(color)
.fillMaxWidth()
.clickable {
colorId = (colorId + 1) % 3
}
.padding(10.dp)
)
}
}
}
Result
In this answer i change selected status but it can easily be applied. I used int to keep Compose class Color out of ViewModel
@Immutable
data class MyItem(val text: String, val colorId: Int = 0)
ViewModel implementation
class MyViewModel : ViewModel() {
private val myItems = mutableStateListOf<MyItem>()
.apply {
repeat(20) {
add(MyItem(text = "Row $it"))
}
}
fun items() = myItems
fun toggleSelection(index: Int) {
val item = myItems[index]
val newColorId = (item.colorId + 1) % 3
myItems[index] = item.copy(colorId = newColorId)
}
}
Usage
@Composable
fun MyComposable(viewModel: MyViewModel) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
itemsIndexed(items = viewModel.items()) { index: Int, item: MyItem ->
val color = when (item.colorId) {
1 -> {
Color.Blue
}
2 -> {
Color.Green
}
else -> {
Color.Red
}
}
Text(text = item.text,
modifier = Modifier
.background(color)
.fillMaxWidth()
.clickable {
viewModel.toggleSelection(index)
}
.padding(10.dp)
)
}
}
}
Other alternative is using SnapshotStateMap
which contains indices and color as Int or as compose Color and update index with next color
val map: SnapshotStateMap<Int, Int> = remember {
mutableStateMapOf()
}
Upvotes: 5
Reputation: 144
You can use remember
in forEach
block
Column {
strings.forEach { color ->
var _color by remember { mutableStateOf(color.value) }
Button(
onClick = {
_color = when (_color) {
Color.Red -> Color.Green
Color.Green -> Color.Blue
Color.Blue -> Color.Red
else -> Color.Red
}
},
colors = ButtonDefaults.buttonColors(containerColor = _color)
) {
Text(color.key, color = Color.White)
}
}
}
Upvotes: 0