Recently I discovered a flow behaviour that I couldn't understand.
The Problem Description
Consider this situation:
you have a parent flow
and inside its collect
, you'll have a "child" flow
and calling .collect()
, like this:
parentFlow.collect {
childFlow.collect()
}
Then if the parentFlow emits some values, the childFlow.collect()
will not be called.
What I have tried
By doing some searches on SO I found the following questions are similar to mine:
- Android: collecting a Kotlin Flow inside another not emitting
- Chaining Flows (collecting a Flow within the collect{} block of another Flow)
However I intend to dig deeper about what's the reason behind this behaviour, therefore I have created a project to reliably reproduce it, you can check it out on github: https://github.com/dumbfingers/Playground/tree/flow-inside-flow
In this mini-repro, I have put some log output. When you click the button, the expect log output should be:
Test: onArrayUpdated
Test: item: {some number}
Test: item: {some number}
Test: onFlowChanged
Test: repoObserve delayed
Test: item: {some number}
Test: item: {some number}
Test: repoAnotherObserve delayed
However, the actual result is:
Test: onArrayUpdated
Test: item: {some number}
Test: item: {some number}
Which indicates these two collect
call inside the randomFlow
are not called:
repository.repoObserve(list).collect {
repository.repoAnotherObserve().collect()
}
Question
In this SO: Android: collecting a Kotlin Flow inside another not emitting The answer suggested that "collecting infinite flow" cause this issue.
And in my experiment, either
- changing
repoObserve
andrepoAnotherObserve
tosuspend
method and making them not returning a flow
or
- use
combine
to combine these flows
will solve this problem.
But why does collect flow inside another flow's collect won't work?
CodePudding user response:
You can launch a Coroutine inside parent collect { ... } block
val scope: CoroutineScope = ...
parentFlow.collect {
scope.launch {
childFlow.collect()
}
}
or
parentFlow.flatMapMerge { childFlow }.collect {
// logic here ...
}
You can also replace flatMapMerge
with flatMapLatest
/flatMapConcat
/flatMapFirst
(FlowExt library)
Hope to help you
CodePudding user response:
Your click listener generates the exact same array list of two numbers each time it’s clicked because you are using a new Random instance with the same seed value of 10 each time. You’re passing them to a StateFlow, and StateFlows have the same behavior as distinctUntilChanged()
. They won’t emit a value that is equal to the last emitted value.
I think if you remove the parameter from your Random constructor call, you will get your expected output. This is because your innermost-called flow is not infinite like in the questions you linked.