louiscad
05/13/2024, 1:23 PMState<Boolean>
, and I want to get a MutableIntState
(or a State<Int>
) that counts the times the boolean became true
.
I want to avoid recomposing/re-rendering when the boolean becomes false
, so I think derivedStateOf
is part of the solution, but I haven't found how to make it aware of its last value… I am thinking about making a class that wraps a var
with that counter, but that seems ugly to me.
Any other ideas?vide
05/13/2024, 1:33 PMvide
05/13/2024, 1:34 PMlouiscad
05/13/2024, 1:43 PMisTrue
is backed by a State<Boolean>
) :
var count by remember {
MutableStateFlow(0)::value
}
val becameTrueCounter = remember {
derivedStateOf {
if (isTrue) ++count else count
}
}
yschimke
05/13/2024, 1:47 PMZach Klippenstein (he/him) [MOD]
05/13/2024, 2:34 PMZach Klippenstein (he/him) [MOD]
05/13/2024, 2:35 PMvide
05/13/2024, 2:36 PMvide
05/13/2024, 2:38 PMZach Klippenstein (he/him) [MOD]
05/13/2024, 2:45 PMZach Klippenstein (he/him) [MOD]
05/13/2024, 2:54 PMChuck Jazdzewski [G]
05/13/2024, 4:24 PMFlow<T>
which was designed to track events not state. You then can use collectAsState()
to take a point-in-time snapshot of the flow when needed in composition which samples the flow at a particular point in time.louiscad
05/13/2024, 5:14 PMChuck Jazdzewski [G]
05/13/2024, 6:04 PMfalse
to true
and never back to false
then a normal mutableStateOf<Boolean>()
works fine. If it isn't monotonic then the snapshot system will only be able to tell you if it was written to and what the current value is. If it appears in a apply changes set then it depends on the mutation policy why it is there. For an structural policy it will only appear there if was ever written to with a different value than current if the initial value is false
then you can assume if it is ever sent then it was sent because it was changed, at some point, from false
to true
but that only works for Booleans as they only have two states.
In general, the snapshot system cannot be used for anything that requires some A to occur whenever some B occurs. It can only ensure A will happen if B may have occurred and then only if a snapshot was applied while B was in the desired state.
BTW, never mutate state in the callback from a dervivedStateOf
as the example above has. The callback function should be a pure function of the state it reads. Anything else is undefined behavior.Zach Klippenstein (he/him) [MOD]
05/13/2024, 6:33 PMderivedStateOf
that took the previous state, then you could avoid invalidating readers when it changes back to false. But if you don’t need the count, then you have some more options.
E.g. Something like this could work. It’s two state reads per value call while the boolean is initially false, but then only a single state read. If your dependencies toggle very frequently (which is the main reason to consider using derivedStateOf
at all anyway) then the cost of two state reads initially might be worth avoiding future invalidations, and it will quickly change to a single state read. When it changes it will be reported as a state write and invalidate other readers, but this will only happen once. You can avoid that by wrapping “state read 1” in a Snapshot.withoutReadObservation
(which adds some cost) or if you implement a custom StateObject
. This approach could be used for any type of value that you want to stop invalidating when it becomes a certain thing.
class DerivedStateLatch(
private val holdAt: Boolean = true,
private val calculate: () -> Boolean
): State<Boolean> {
private var stateHolder: State<Any> by mutableStateOf(derivedStateOf(calculate))
override val value: Boolean
get() {
// State read 1: Figure out if we've reached the holdAt value yet or not.
val stateValue = this.stateHolder
if (stateValue is Boolean) {
// The holdAt value has already been reached, return it.
return stateValue
}
// State read 2: The holdAt value has not been reached yet, but might be on
// this read.
val currentValue = (stateValue as State<Boolean>).value
if (currentValue == holdAt) {
// The holdAt value was reached for the first time. Replace the derivedStateOf
// inside stateHolder with the fixed value so on the next get() call we hit
// the fast path above.
this.stateHolder = currentValue
}
return currentValue
}
}
However, it’s possible for the value to become true, then false again, without this code (or any code) ever seeing the true value. If you need that guarantee, you’d need to either use a custom state object or do something with flows.louiscad
05/14/2024, 5:32 AMlouiscad
05/14/2024, 5:36 AMMutableState*Flow*
is fine and the shortest thing for my use case, isn't it?louiscad
05/14/2024, 5:40 AMGraphicsLayer
after the app comes back visible from the background, as they seem to be evicted from the video memory and render empty if I don't make sure they are recorded again on coming back visible (started/resumed lifecycle state).
That means missing when the value becomes true and becomes false very quickly is actually a nice feature to have, to avoid re-recording needlessly, though I don't think it'd really happen in real-life.Zach Klippenstein (he/him) [MOD]
05/14/2024, 4:39 PMZach Klippenstein (he/him) [MOD]
05/16/2024, 6:25 PMThere were recent fixes that were landed around this
louiscad
05/16/2024, 10:28 PMNader Jawad
05/16/2024, 10:30 PMNader Jawad
05/16/2024, 10:30 PMlouiscad
05/16/2024, 10:58 PMlouiscad
05/16/2024, 11:07 PMlouiscad
05/16/2024, 11:08 PM