AmrJyniat
08/26/2022, 9:45 AMLaunchEffect()
has natural behavior like distinctUntilChanged()
?
I have an event for showing toast but it seems that show the toast only once.
Code in 🧵// Event in VM
private val _event = Channel<LoginEvent>()
val event = _event.receiveAsFlow().shareIn(viewModelScope)
fun sendEvent(newEvent: LoginEvent) = viewModelScope.launch {
_event.send(newEvent)
}
// Collect events in Composable
val uiEvent by viewModel.event.collectAsStateWithLifecycle(NoEvent)
LaunchedEffect(uiEvent) {
when (uiEvent) {
is FailedLoginEvent ->
showToast(uiEvent.errorMsg) //showing toast only once even when sending a new event
....
}
}
Csaba Szugyiczki
08/26/2022, 10:39 AMLaunchedEffect
does not change, then it will not trigger until the given value changes. I guess your LoginEvent is an enum or object, for which every value is basically referring to the same instance, hence your LaunchedEffect only runs the first time you set your event valueStylianos Gakis
08/26/2022, 10:40 AMLaunchedEffect
. collectAsState
uses produceState
under the hood which itself returns a State
which by design is not built to re-emit the same result twice, that’s a way compose skips recomposing stuff when it doesn’t need to.
So assuming that you want to keep your event in a Channel (maybe you don’t)
You’d have to do something like this instead:
val lifecycle = LocalLifecycleOwner.current.lifecycle
LaunchedEffect(viewModel.eventFlow) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.eventFlow.collect { event ->
doStuffWith(event)
}
}
}
collectAsState
on them and relying on the equality check to turn false so that you get a new instance, so that the LaunchedEffect key changes, so that it gets recreated sounds like a lot of extra steps that you don’t really want to do when you can simply do what I am doing above. This avoids going from flow into the compose State and then back to collecting a flow and directly takes the flow and collects it in a life-cycle aware way.
What are your thoughts on this Csaba?Csaba Szugyiczki
08/26/2022, 11:03 AMStylianos Gakis
08/26/2022, 11:08 AMCsaba Szugyiczki
08/26/2022, 11:14 AM@Composable
fun <T> FlowCollector(flow: Flow<T>, collector: ((T) -> Unit){
val lifecycle = LocalLifecycleOwner.current.lifecycle
LaunchedEffect(flow) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
flow.collect { event ->
collector(event)
}
}
}
}
AmrJyniat
08/26/2022, 11:18 AMsealed interface LoginEvent
data class FailedLoginEvent(val errorMsg: String) : LoginEvent
....
LoginEvent
is normal sealed interface with multiple events type, I'm sending a new event with `sendEvent()`that launch a new coroutine each time, so I'm sure that the problem in the consumer not producer.Stylianos Gakis
08/26/2022, 11:21 AMcollector
changes after the first call, the collect would be referencing a stale parameter, since you’re not keying the LaunchedEffect with it. And with that thought, I think to be technically correct, one would also want to key on the lifecycle too. I don’t know if that ever changes, but if it does that would also be referencing the old reference since in my suggestion that’s not keyed either.Csaba Szugyiczki
08/26/2022, 11:25 AMFailedLoginEvents
both with the same errorMsg
their equals method will return true, so technically they will be 2 different instances, but representing the same value, so it will not trigger a recomposition.
Are you emitting the same error? Could you try emitting two different errors to see if it works?AmrJyniat
08/26/2022, 11:28 AMuiEvent
gets collected twice in your solution, inside LaunchEffect()
and when collecting it as a normal flow, what do you think?Stylianos Gakis
08/26/2022, 11:31 AMLaunchEffect
are just used as “keys” to know when to restart the LaunchedEffect
. There is no collection happening there. It’s a way to tell that if somehow the instance of viewmodel.eventFlow
changes, then this LaunchedEffect
should restart. In practice maybe this never happens here, but it’s always good to key the things that you’re using inside the LaunchedEffect
.Csaba Szugyiczki
08/26/2022, 11:33 AM