Jolly Wojak
Jolly Wojak

Reputation: 53

Android Jetpack Compose: Composable function doesn't recompose after mutable state changes

I've a pager composed of cards and I want to control each card's border with a boolean variable: green if that variable's true, darkgrey else.

        var isPedestrianSelected by remember { mutableStateOf(false) }
        var isCyclistSelected by remember { mutableStateOf(false) }
        var isCarSelected by remember { mutableStateOf(false) }

        Card(
            shape = RoundedCornerShape(70.dp),
            border = BorderStroke(
                1.2.dp,
                when (page) {
                    0 -> { if (isPedestrianSelected) Color.Green else Color.DarkGray }
                    1 -> { if (isCarSelected) Color.Green else Color.DarkGray }
                    2 -> { if (isCyclistSelected) Color.Green else Color.DarkGray }
                    else -> { Color.DarkGray }
                }
            ),
            elevation = CardDefaults.cardElevation(
                defaultElevation = 12.dp
            ),
            onClick = {
                val stationType = when (page) {
                   0 -> StationType.PEDESTRIAN
                   1 -> StationType.PASSENGER_CAR
                   2 -> StationType.CYCLIST
                   else -> { StationType.PEDESTRIAN }
                }
                when (stationType) {
                    StationType.PEDESTRIAN -> {
                        isPedestrianSelected = true
                        isCarSelected = false
                        isCyclistSelected = false
                    }
                    StationType.PASSENGER_CAR -> {
                        isPedestrianSelected = false
                        isCarSelected = true
                        isCyclistSelected = false
                    }
                    StationType.CYCLIST -> {
                        isPedestrianSelected = false
                        isCarSelected = false
                        isCyclistSelected = true
                    }
                    else -> {}
                }
                ...
            },

Issue: when the boolean variable (e.g. isCarSelected) is set to true, the borders are colored correctly (green), but when it returns to false, the borders won't change (although I'd expect they would change back to the darkgrey color).

Thanks for help

EDIT: Maybe I can explain better. My purpose is that at most one card of the pager has a green border, so what my code should do is, if I select one page, then put a green border around the card of that page and remove all green borders around cards of other pages. Example: isCarSelected = true, so the card on page 1 is set to a green border. After scrolling the pager, I click on another page, say page 0, and this sets isPedestrianSelected=true and other booleans to false, such as isCarSelected = false, and this should turn page 1 color from green to darkgray (along with turning page 0 color from darkgray to green). But this doesn't happen for some reason.

Upvotes: 1

Views: 117

Answers (2)

Darryl Johnson
Darryl Johnson

Reputation: 950

The reason is in your onClick function, you only ever set the target value to true and never to false.

This should work:

when (stationType) {
    StationType.PEDESTRIAN -> {
        isPedestrianSelected = if(isPedestrianSelected) false else true
        isCarSelected = false
        isCyclistSelected = false
    }
    StationType.PASSENGER_CAR -> {
        isPedestrianSelected = false
        isCarSelected = if(isCarSelected) false else true
        isCyclistSelected = false
    }
    StationType.CYCLIST -> {
        isPedestrianSelected = false
        isCarSelected = false
        isCyclistSelected = if(isCyclistSelected) false else true
    }
    else -> {}
}

However, I believe you could improve the code overall by maintaining one nullable mutable state which is updated on click where null represents none selected.

var selectedStationType: StationType? by remember { mutableStateOf(null) }

Upvotes: 0

BenjyTec
BenjyTec

Reputation: 10333

The problem with your current code is that you declare your isPedestrianSelected / isCarSelected / isCyclistSelected variables within the content of the HorizontalPager. So each page will have its own three local variables.

Move your declaration on top of the HorizontalPager, then your code should work:

var isPedestrianSelected by remember { mutableStateOf(false) }
var isCyclistSelected by remember { mutableStateOf(false) }
var isCarSelected by remember { mutableStateOf(false) }

HorizontalPager(
    // ...
) { page ->

    Card(
        //...
    )
}

Note that you could simplify the logic of your implementation by instead using only one variable to store the index of the page where the card currently has a green border:

var selectedCardIndex by rememberSaveable { mutableStateOf(-1) }

HorizontalPager(
    //...
) { page ->
    
    Card(
        shape = RoundedCornerShape(70.dp),
        border = BorderStroke(
            1.2.dp,
            if (page == selectedCardIndex) Color.Green else Color.DarkGray
        ),
        elevation = CardDefaults.cardElevation(
            defaultElevation = 12.dp
        ),
        onClick = {
            selectedCardIndex = page
        },
        //...
    )
}

Upvotes: 0

Related Questions