Erik
12/15/2021, 11:17 PMStateFlow<State>
) in two fragments in the same activity.
As can be seen in the logcat at runtime, only one of the two fragments receives new state values. I don't understand why, because a StateFlow
should emit to all collectors, not just one. Why is that? What am I doing incorrectly?gildor
12/15/2021, 11:46 PMgildor
12/15/2021, 11:47 PMgildor
12/15/2021, 11:49 PMErik
12/16/2021, 8:47 AMMain
dispatcher (possibly the immediate
variant). This is a single threaded looper. Is it possible that the first fragment collects the state and consumes it (notifies view model) and the view model resets the state, all in the same main thread loop, while the second fragment's collector is suspended until a future loop is available? If so, then it doesn't surprise me how this behaves, because in the next loop the state is back to what it was before, and equal states aren't emitted by StateFlow
.
I'll have to try later if I can post {}
the state consumption, or use a multi-threaded dispatcher, or a suspending function before consuming the event (e.g. delay
).gildor
12/16/2021, 8:55 AMIs it possible that the first fragment collects the state and consumes itConsume event?
gildor
12/16/2021, 8:56 AMI’ll have to try later if I canIf it somehow caused by immediate, just do not use immediate, use Dispatchers.Main explicitly Still I’m not quite understand the problem, if state is consumed and moved to another state, why is it bad? Because both fragments just should show some existing statethe state consumption, or use a multi-threaded dispatcher, or a suspending function before consuming the event (e.g.post {}
). (edited)delay
Erik
12/16/2021, 9:15 AMErik
12/16/2021, 9:16 AM<http://view.post|view.post> { }
and now the logcat shows that both fragments collect all state updates. This confirms (but doesn't prove) my theoryErik
12/16/2021, 9:19 AMdelay(50)
(instead of post
) before consuming the event, that allows the both fragments to collect all states too. Another confirmationErik
12/16/2021, 9:21 AMwithContext(Dispatchers.Default) { }
which is multi-threaded (and a suspending function, but that shouldn't matter here?), also both fragments observe all state updatesJoost Klitsie
12/16/2021, 12:52 PMJoost Klitsie
12/16/2021, 12:53 PMJoost Klitsie
12/16/2021, 12:54 PMJoost Klitsie
12/16/2021, 12:54 PMJoost Klitsie
12/16/2021, 12:55 PMJoost Klitsie
12/16/2021, 12:56 PMJoost Klitsie
12/16/2021, 12:57 PMJoost Klitsie
12/16/2021, 12:57 PMJoost Klitsie
12/16/2021, 12:59 PMprivate val eventChannel = Channel<LoginContract.Event>(Channel.BUFFERED)
override val events = eventChannel.receiveAsFlow()
// collecting
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.events.collect { event ->
// do something here
}
}
Joost Klitsie
12/16/2021, 1:00 PMJoost Klitsie
12/16/2021, 1:02 PMErik
12/16/2021, 7:49 PMJoost Klitsie
12/17/2021, 6:59 AMJoost Klitsie
12/17/2021, 6:59 AMJoost Klitsie
12/17/2021, 7:01 AMI then discovered the surprising (to me) behaviour that a state flow collector doesn't observe all state values even if the state changed!
Joost Klitsie
12/17/2021, 7:01 AMJoost Klitsie
12/17/2021, 7:02 AMJoost Klitsie
12/17/2021, 7:02 AMErik
12/17/2021, 7:03 AMErik
12/17/2021, 7:06 AMErik
12/17/2021, 7:07 AMErik
12/17/2021, 7:07 AMJoost Klitsie
12/17/2021, 7:52 AMcollect
this part what actually shows the behavior:
if (oldState == null || oldState != newState) {
collector.emit(NULL.unbox(newState))
oldState = newState
}
This case will happen if you change the state while you collect in an Unconfined or Immediate dispatcher
Also, I did check the thread in your link and the example about undelivered items, and it was interesting to see that in that specific case it was reproducable to undeliver events 🙂 but when I made all the coroutines fire from the same context I couldn't reproduce, and in viewmodel + view world that will normally be the case.Joost Klitsie
12/17/2021, 7:54 AM