Zoltan Demant
11/08/2021, 11:42 AMflow.collectAsState
inside a composable, if the flow I pass in changes, the last value from the old flow seems to be collected again before the new flow kicks off. đ§ľđZoltan Demant
11/08/2021, 11:46 AMLaunchedEffect(flow){
flow.collect{
// Collects 0 from first, 1 from second
}
}
I dont know if it matters, but the stateFlow is a timer like this:
flow {
val context = currentCoroutineContext()
while (context.isActive) {
val duration = measureTimeMillis {
emit(definition.tick())
}
val delay = definition.interval() - duration
delay(delay)
}
}.stateIn(
scope = scope,
started = WhileSubscribed(),
initialValue = definition.tick()
)
There are several timers like this. In this specific scenario a new timer is created, hence a new flow is passed into my composable. Its not the end of the world, but I would like to understand why it happens if anyone can enlighten me.
This also works like LaunchedEffect.
val tick by key(timer) {
timer.collectAsState()
}
Csaba KozĂĄk
11/08/2021, 11:56 AMcollectAsState()
? Can you give us more context?Zoltan Demant
11/08/2021, 12:02 PMval tick by timer.collectAsState()
Csaba KozĂĄk
11/08/2021, 12:06 PMZoltan Demant
11/08/2021, 12:08 PMCsaba KozĂĄk
11/08/2021, 12:18 PMtimer
?Zoltan Demant
11/08/2021, 12:21 PMflow {}
block already.
data class Timer internal constructor(
private val active: Boolean,
private val flow: StateFlow<TimerTick>
) : StateFlow<TimerTick> by flow
Its basically a flow that ensures it only emits 1 tick per second.
data class TimerTick internal constructor(
val duration: Long,
val remainder: Float,
val overdue: Boolean
)
Csaba KozĂĄk
11/08/2021, 12:22 PMtimer
what you collect? Is it a property in the VM?Zoltan Demant
11/08/2021, 12:25 PMCsaba KozĂĄk
11/08/2021, 12:36 PMval tick by timer.collectAsState()
. Your UI should collect exactly what the flow emits. Try to put onEach { }
before collectAsState()
and debug if your flow works correctly.Zoltan Demant
11/08/2021, 1:44 PMcollectAsState
). Although Im not sure if this is a bug, or just semantics for how flows are collected in compose, two different State<TimerTick> are created with the collectAsState way of doing it, which in turn could be causing the last value to be shown again before 1?Csaba KozĂĄk
11/08/2021, 1:49 PMfirst flow
? Donât you have just one flow { }
, saved in a property and collected?Zoltan Demant
11/08/2021, 1:56 PMFlow<SomeScreen>
which I collectAsState for rendering, and inside SomeScreen
I have a Timer
property, whenever I create a new timer, a SomeScreen is emitted with it, and in turn my composable function with the above logic is invoked.Csaba KozĂĄk
11/08/2021, 1:58 PMZoltan Demant
11/08/2021, 2:01 PMCsaba KozĂĄk
11/08/2021, 2:04 PMZoltan Demant
11/08/2021, 2:07 PMJeff
11/08/2021, 2:47 PMZach Klippenstein (he/him) [MOD]
11/08/2021, 5:08 PMproduceState
does not treat the initial value as a key (see here), with the workaround being to use the key(){}
function. There was a discussion about this a while back, and a related bug was filed. That bug is specifically for produceState
though, and I donât see an argument against making collectAsState
behave differently so maybe itâs worth filing another bug.Zoltan Demant
11/09/2021, 6:55 AMkey
workaround, since it doesnt feel like the natural way of using it I filed a bug. I dont have my hopes up considering working-as-intended of produceState, but at least a discussion can be held around it - Im probably not the first or last to run into this.Zoltan Demant
11/09/2021, 3:11 PMcollectAsState
will also re-emit the initial value when returning to the composition, regardless of using the key
block, Id expect to see the last emitted value if anything. Perhaps this also sheds some additional light on produceState
? In either case, Id be surprised if this was not a bug! mind blownAlexander Maryanovsky
05/27/2022, 4:24 PMcollectAsState
to not return a State
with an old value. Was very confused when I discovered it. You can literally have
MutableStateFlow(n).collectAsState().value
being something other than n
.Zach Klippenstein (he/him) [MOD]
05/27/2022, 5:02 PMAlexander Maryanovsky
05/27/2022, 5:39 PMcollectAsState
doing that.
Thinking about it more, it could even be a security problem. What if the function where collectAsState
is called runs a SideEffect
that sends the data somewhere (like in https://developer.android.com/jetpack/compose/side-effects#sideeffect-publish)? Now, if the function is, for example,
fun handleSensitiveData(targetUrl: Url, sensitiveData: StateFlow<String>)
you can end up sending sensitive data to the wrong URL.Alexander Maryanovsky
06/23/2022, 5:29 PMAlexander Maryanovsky
06/24/2022, 3:42 AM