Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

  • 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?

like image 867
dumbfingers Avatar asked Sep 16 '25 04:09

dumbfingers


1 Answers

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

like image 99
Petrus Nguyễn Thái Học Avatar answered Sep 17 '25 17:09

Petrus Nguyễn Thái Học