Home > Enterprise >  Kotlin flow inside flow, the parent flow does not call collect
Kotlin flow inside flow, the parent flow does not call collect

Time:03-25

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

  • changing repoObserve and repoAnotherObserve to suspend 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.

  • Related