https://kotlinlang.org logo
Title
t

Travis Griggs

04/06/2023, 5:37 PM
I think I must not be getting a piece of the state hoisting puzzle. I have a UI which is a list (lazy) of cards or status, accompanied by a header with buttons at the top that can tune visibility options for things that show up in the cards. To wit, my basic structure looks something like:
Column {
    var visibilityOptions by remember { mutableStateOf(VisibilityOptions()) }
    Header(... options = visibilityOptions)
    LazyColumn {
        items(things) { each -> 
            ItemCard(each, ... options = visibilityOptions)
        } 
    }
}
Half of this works. The header which opens a dialog to tune the flags in visibility options seems to stay up to date with changes made. But the ItemCards don't recompose. I wondered if this was a case where I needed to pass the actual observable instead of its value (the whole mechanism that compose compiler uses to figure out how variables retrigger compositions is sort of black magic to me at the moment), so I tried changing that to
var visibilityOptionsState = remember { mutableStateOf(VisibilityOptions()) }
and then adding all of the various
.value
accesses. But i get the same result. No recomposition of the ItemCards. What am I missing?
k

Kirill Grouchnikov

04/06/2023, 5:42 PM
What is visibility options? Does it have a map or a list inside? Those might need to be tracked differently
t

Travis Griggs

04/06/2023, 5:43 PM
data class VisibilityOptions(
   var showID: Boolean = false, var showPower: Boolean = false, var showSignal: Boolean = false
)
When I scroll the list, the newly created rows pick up the changes
k

Kirill Grouchnikov

04/06/2023, 5:44 PM
Probably needs val and usage of copy when fields need to change
t

Travis Griggs

04/06/2023, 5:47 PM
@Composable
fun ValveRemotePrimaryRow(
   valve: ValveRemote,
   optionsState: MutableState<VisibilityOptions> = mutableStateOf(VisibilityOptions())
) {
   Row(...) {
      SolenoidPositionBox(valve = valve)
      TitleBox(namely = valve, modifier = Modifier.weight(1f), showID = optionsState.value.showID)
      AnimatedVisibility(visible = optionsState.value.showSignal) {
         SignalBox(...)
      }
      AnimatedVisibility(visible = optionsState.value.showPower) {
         BatteryBox(...)
      }
   }
}
That's an abbreviated version of my "cards"
p

Pablichjenkov

04/06/2023, 6:17 PM
Same if you try with key? items { key { }}
o

orangy

04/06/2023, 6:21 PM
If you change vars inside
VisibilityOptions
, Compose has no means to track these changes. You can either use immutable data class as Kirill said, and change the
value
in the
visibilityOptions
state, or you can replace simple vars with mutable state inside options, like
var showPower: Boolean by mutableStateOf(false)
t

Travis Griggs

04/06/2023, 6:36 PM
Thanks for the hints and pokes guys. Subtle misuse of the
apply
function. I did indeed have the vars, and was doing the following when updating in my header:
optionsState.value = options.apply { showPower = showPower.not() }
which updated the options struct/dataclass. But since
apply
returns context object, no "change" was triggered in the state container. I've switched to using the implicit copy(...) when updating my options thing now and now everything works. So, in the end, I understood the pattern better than I thought I did, but just didn't drive Kotlin correctly 😕