Search code examples
android-jetpack-composeandroid-buttonmutablestateof

Text and Button Color not updating when MutableState object changes


For the below code, I'm trying to get the selectedColorsText to update whenever a user clicks on a button. The button background should then turn black and the text inside it white once selected. If de-selected, the button should turn back to white and the text inside it black. However, neither the selectedColorsText or the button colors are updating as expected. What am I doing wrong?

enum class ColorsEnum(val rawValue: String) {
    red("Red"),
    orange("Orange"),
    yellow("Yellow"),
    green("Green"),
    blue("Blue")

}


@Composable
fun LazyGridView() {

    var selectedColors by remember { mutableStateOf(arrayListOf<String>()) }
    var selectedColorsText by remember { mutableStateOf("") }

    Column (
        verticalArrangement = Arrangement.spacedBy(10.dp)
    )
    {

        Text("Selected Colors: $selectedColorsText")


        LazyVerticalGrid(
            columns = GridCells.Fixed(2),
            verticalArrangement = Arrangement.spacedBy(10.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            items(ColorsEnum.values()) { color ->

                Button(
                    onClick = {
                        if (selectedColors.contains(color.rawValue)) {
                            selectedColors.remove(color.rawValue)
                            selectedColorsText = selectedColors.joinToString(", ")

                            Log.d("selectedColors = ", "$selectedColors")
                            Log.d("selectedColorsText = ", "$selectedColorsText")

                        } else {
                            selectedColors.add(color.rawValue)

                            selectedColors.sortBy { colorName ->
                                ColorsEnum.values()
                                    .find { it.rawValue == colorName }
                                    ?.let(ColorsEnum.values()::indexOf)
                            }

                            Log.d("selectedColors = ", "$selectedColors")
                            Log.d("selectedColorsText = ", "$selectedColorsText")

                        }
                    },
                    modifier = Modifier
                        .border(
                            width = 1.dp,
                            color = Color.Black,
                            shape = RoundedCornerShape(5.dp)
                        ),
                    colors = ButtonDefaults.buttonColors(
                        backgroundColor =
                        if (isSystemInDarkTheme()) {
                            if (selectedColors.contains(color.rawValue)) {
                                Color.White
                            } else {
                                Color.Black
                            }
                        } else {
                            if (selectedColors.contains(color.rawValue)) {
                                Color.Black
                            } else {
                                Color.White
                            }
                        }
                    ),

                    ) {

                    Text(
                        text = color.rawValue,
                        style = TextStyle(
                            fontWeight = FontWeight.Bold,
                            color =
                            if (isSystemInDarkTheme()) {
                                if (selectedColors.contains(color.rawValue)) {
                                    Color.Black
                                } else {
                                    Color.White
                                }
                            } else {
                                if (selectedColors.contains(color.rawValue)) {
                                    Color.White
                                } else {
                                    Color.Black
                                }
                            }
                        ),
                    )


                }

            }
        }
    }

}

Solution

  • The main problem in your code is that you are trying to change the state, not its contents. From your code I made a simple example. In addition, try to look at the UDF (Unidirectional Data Flow) approach, it will help a lot in understanding how to work with a compose

    class MainActivity : ComponentActivity() {
    
        // Create flow in ViewModel layer
        private val _selectedColors = MutableStateFlow<List<ColorsEnum>>(listOf())
        val selectedColors: StateFlow<List<ColorsEnum>> = _selectedColors
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                TestApplicationTheme {
    
                    // Subscribe on "selectedColors" flow in compose layer
                    val selectedList by selectedColors.collectAsStateWithLifecycle()
    
                    Grid(selectedList, ::processColor)
                }
            }
        }
    
        private fun processColor(color: ColorsEnum) {
            // update selected colors in ViewModel layer
            _selectedColors.update { selected ->
                if (selected.contains(color)) {
                    selected - color
                } else {
                    selected + color
                }
            }
        }
    }
    
    enum class ColorsEnum(val rawValue: String) {
        RED("Red"),
        ORANGE("Orange"),
        YELLOW("Yellow"),
        GREEN("Green"),
        BLUE("Blue")
    }
    
    @Composable
    private fun Grid(selectedColors: List<ColorsEnum>, onClick: (ColorsEnum) -> Unit) {
        LazyVerticalGrid(
            columns = GridCells.Fixed(2),
            verticalArrangement = Arrangement.spacedBy(10.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            items(ColorsEnum.values()) { color ->
    
                GridItem(
                    color = color,
                    isChecked = selectedColors.contains(color),
                    // also look on command pattern for process different 
                    // kinds callback and data from your UI
                    onClick = onClick
                )
            }
        }
    }
    
    @Composable
    private fun GridItem(
        color: ColorsEnum,
        isChecked: Boolean,
        onClick: (ColorsEnum) -> Unit
    ) {
        Button(
            modifier = Modifier
                .border(
                    width = 1.dp,
                    color = Color.Black,
                    shape = RoundedCornerShape(5.dp)
                ),
            colors = ButtonDefaults.buttonColors(
                backgroundColor =
                if (isSystemInDarkTheme()) {
                    if (isChecked) {
                        Color.White
                    } else {
                        Color.Black
                    }
                } else {
                    if (isChecked) {
                        Color.Black
                    } else {
                        Color.White
                    }
                }
            ),
            onClick = {
                onClick.invoke(color)
            },
        ) {
            Text(
                text = color.rawValue,
                color = if (isSystemInDarkTheme()) {
                    if (isChecked) {
                        Color.Black
                    } else {
                        Color.White
                    }
                } else {
                    if (isChecked) {
                        Color.White
                    } else {
                        Color.Black
                    }
                }
            )
        }
    }