Fixed
Status Update
Comments
il...@google.com <il...@google.com> #2
Yigit, do you have time to fix it?
reemission of the same liveData is racy
reemission of the same liveData is racy
ja...@google.com <ja...@google.com> #3
yea i'll take it.
il...@google.com <il...@google.com> #4
Thanks for the detailed analysis. This may not be an issue anymore since we've started using Main.immediate there but I' not sure; I'll try to create a test case.
il...@google.com <il...@google.com>
ap...@google.com <ap...@google.com> #5
just emitting same live data reproduces the issue.
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
jb...@google.com <jb...@google.com> #6
With 2.2.0-alpha04 (that use Main.immediate), the issue seems to be still there (I tested it by calling emitSource() twice, like your test case)
pr...@google.com <pr...@google.com> #7
yea sorry immediate does not fix it.
I actually have a WIP fix for it:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1112186
if your case is the one i found (emitting same LiveData multiple times, as shown in #5) you can work around it by adding a dummy transformation.
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData.map {it })
emitSource(subLiveData.map {it} )
}
I actually have a WIP fix for it:
if your case is the one i found (emitting same LiveData multiple times, as shown in #5) you can work around it by adding a dummy transformation.
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData.map {it })
emitSource(subLiveData.map {it} )
}
Description
Component used: Fragment
The new "predictive back" feature has surfaced some interesting issues when dealing with the Fragment backstack. In general, there seems to be a mismatch in expectations as to whether a "destroyed" instance of a Fragment will be re-created or not.
On one hand, when the FragmentManager's predictive back detects that the user canceled the gesture, it will take the Fragment instance that was previously destroyed and add it back to the top of the stack, driving the create/start/resume lifecycle again. It does this by by re-committing the FragmentTransaction that added it .
On the other hand, utilities like the lifecycle-bound coroutine scope do not handle the state re-entering "creation" after its destruction. The scope is created once here and when the destroyed state is reached the supervisor job is canceled and the lifecycle observer is removed.
These two behaviors are incompatible, if I'm following this code correctly.
Technically, the destruction of a Fragment does not prohibit its reuse, and so the FragmentManager behavior is not wrong from what I can tell. However, in practice I can imagine that reusing Fragments this way will inherently lead to bugs that will be difficult to diagnose whenever a lifecycle-dependent behavior assumes that destruction is final. In essence, this introduces a lifecycle transition (destroyed -> created) that many fragments may not be designed to support.
If instead the FragmentManager recreates the Fragment from scratch, that would avoid these issues but there will likely be negative performance implications.
It's worth noting that a user may in fact trigger a predictive back gesture without realizing it. In some cases, a tap near the side edge of the device will trigger the beginning and cancellation of the gesture, without any visual cue that this happened. That will exacerbate the difficulty of reproducing and diagnosing these types of lifecycle state bugs.