dumbfingers
dumbfingers

Reputation: 7089

Kotlin flow inside flow, the parent flow does not call collect

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:

  1. Android: collecting a Kotlin Flow inside another not emitting
  2. 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

or

will solve this problem.

But why does collect flow inside another flow's collect won't work?

Upvotes: 4

Views: 2710

Answers (2)

Tenfour04
Tenfour04

Reputation: 93541

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.

Upvotes: 0

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

Upvotes: 3

Related Questions