fix: potential skip in repeatOnLifecycle#20430
Conversation
|
I posted this PR before assigning it. Of course I already left the comment that I'd like to figure out this issue, but If I happened to violate Anki's assigning system, please tell me. I'll follow that 😉 |
| // tests for the deduplication logic in StateFlow.launchCollectionInLifecycleScope | ||
| // scenario: background > value changes > resume triggers race | ||
| // to check flow logic ensure that all updates are delivered and none are potentially skipped | ||
| class StateFlowLifecycleCollectionTest { |
There was a problem hiding this comment.
Is it feasible to test launchCollectionInLifecycleScope itself, rather than the logic?
There was a problem hiding this comment.
Sure I'll work on that. I'll remove the logic tests and try to add comments in the new test explaining the background of this change. If you want to leave these tests, please let me know. Thanks!
There was a problem hiding this comment.
I don't think they produce value if they can't be broken by changes to the app.
f0f7bfe to
f39272f
Compare
|
I replaced the logic tests with a test directly using I'm a little concerned about using opt-in API, but without it, reproducing this scenario would require adding test specific code (e.g. yield). So I chose this approach to avoid changing origin code. @david-allison when you have time, could you take another look? Thank you! |
repeatOnLifecycle
|
Sorry this one sat. @criticalAY FYI I'm struggling conceptually here: queuing up state updates shouldn't be the cause of a bug: UI shouldn't have dependencies between states. Each state update should describe the 'full' state of the screen. We should be able to If the intermediate updates matter, then we should be treating this as a stream of events, rather than state (which IMO should be a SharedFlow/Channel, not a StateFlow) Feel free to use AI for this one: let me know if I'm wrong, or if there are instances in our codebase where this would make a difference. I'm not certain in my viewpoint here, I feel I'm missing something and facts trump opinion :D |
|
Hello 👋, this PR has had no activity for more than 2 weeks and needs a reply from the author. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically |
|
@criticalAY thoughts welcome |
|
Will get back in few hours, thanks! |
|
The problem isn't about needing intermediate values. It's that the dedup logic reads StateFlow.value inside collect, and .value can race ahead of what's actually being collected. This means our own code can skip the final latest state too, not just intermediates. So the UI ends up stuck on a stale value. I used AI to get an example : ExampleLet me walk through a concrete scenario. Say you have a screen showing a counter value, and your StateFlow dedup logic looks like this: Here's what happens step by step:
The UI never shows If you use (You are right about, that StateFlow is a state holder and well-designed UI state shouldn't depend on intermediate emissions) |
|
@HS0204 Please update the commit message given the above, then let's get this in |
|
Feel free to merge once David's comment is resolved |
- reproduce and verify the concern where lastValue check against StateFlow.value may skip UI updates during lifecycle transitions
- use collected emission `it` instead of `StateFlow.value` so lastValue tracks only what was actually processed When performing manual deduplication inside a collect block, reading StateFlow.value instead of the emitted value (it) allows the tracking variable to "race ahead" of the collector. If multiple updates occur rapidly (e.g., while the UI is in the background), the tracking variable may be updated to the latest value while processing an intermediate emission. This causes the collector to incorrectly drop the subsequent final emission as a duplicate.
f39272f to
bd11dda
Compare
Purpose / Description
A potential issue was discussed in the review of #20192.
If the logic inside the collection block updates
lastValuebefore the UI is actually updated, or if there is a mismatch between howlastValueis persisted and how the flow emits values, a critical UI update may be skipped because the check assumes the state has not changed since the last emission.This PR reproduces the scenario discussed in the review with tests and adds a fix that ensures the corrected behaviour.
Fixes
repeatOnLifecycle#20394Approach
StateFlow.valuechanges during collection, the update for that value was skipped.itinstead ofStateFlow.valueunder the same scenario.launchCollectionInLifecycleScope.How Has This Been Tested?
Test reproducing the potential scenario passed.
Learning (optional, can help others)
This also helped me better understand the behaviour of
StateFlow.value, especially how the current value can diverge from the collected emission when another updates the StateFlow during collection.Before this PR, I assumed that
StateFlow.valuewould always correspond to the value being collected. While looking into this issue, I gained a clearer understanding of the "most recently emitted value" behaviour described in the official docs.Checklist
Please, go through these checks before submitting the PR.