'Why functions that produce LiveData or Flow, don't have to be called from CoroutineScope?

When we usually use Room, we use Kotlin Coroutine and make a DAO to access Room and to get the result. most of functions usually have suspend modifier at the beginning of function but LiveData and Flow. for instance, let's take a look these two code below.

@Query("SELECT * FROM MockTable")
suspend fun allMockDataWithSuspend(): List<MockData>

@Query("SELECT * FROM MockTable")
fun allMockData(): Flow<List<MockData>> // or LiveData<List<MockData>>

when we use suspend modifier, we need to call the function in coroutine scope because the function has suspend modifier. but we don't need to call the function in coroutine when the function's result is LiveData or Flow even though it's I/O access.

How is this possible?



Solution 1:[1]

There is nothing that requires a Flow object to be created in a coroutine context. It's just a matter of creating the object that will eventually yield data asynchronously.

Flow collection (getting the actual Flow results asynchronously) is a different matter entirely.

Solution 2:[2]

A suspending function asynchronously returns a single value, a flow returns multiple asynchronously computed values.

Flows are cold, data is not emitted without an active collector (the code inside the flow builder does not run until collected). That is a key reason why functions that return flows are not suspend functions, as nothing happens until any of the terminal operators is invoked. So at the point of creation of a Flow noone is collecting it, therefore no work is being done. Try creating a flow with either the flow or flowOf functions and you'll see that they're not suspend functions as well. They return quickly without waiting for anything. Intermediary operators also do not trigger flow collection and therefore aren't suspend function as they usually transform upstream flow into a new flow, they're cold as the flows themselves. Terminal operators on the other hand are suspend functions as they start a collection of a flow.

And when it comes to LiveData, you have an initial value which is always null, and Room makes sure that those queries returning LiveData are run on the background thread without you doing it manually.

Solution 3:[3]

The LiveData and the Kotlin's Flows are basically kind of observables, which means something will happen at some point in time and these observables will notify it.

So when we think through the angle of observability, from the user point of view we just need to keep a box ready and open to catch the observables when it arrives, until it comes let the box be closed (suspend state)

Android way of observing is done via LiveData

lveViewmodel.liveData.observe(this, {

  })

Kotlin way of observing is done via Flow ( One of the use-case of flow is acting like an observable)

lveViewmodel.stateFlow.collect {
               //suspendable block
            }

And CoroutineScope is different, it's like a grouping of certain things, here in terms of kotlin we can say grouping on coroutines and controlling them.

Solution 4:[4]

Dao functions with Flow and LiveData are not suspending, they can be called from anywhere.

As to how this works even though its IO access?

You need to understand how async programming works, the fundamental idea behind async programming is the callback. so instead of waiting for the IO call to finish, you register a callback which will be invoked when the response is ready.

In case of LiveData or Flow you specify the callback explicitly. the function call doesn't block(no need for thread offloading), it simply asks for some data and register a callback, which should be called when data is ready

dao.allMockData().collect { data ->
     // This is the callback
}

dao.allMockData().observe(lifeCycleOwner, Observer {
    // This is the callback
}

In case of a suspend function the callback is implicit, kotlin takes care of its creation and call

coroutineScope.launch{
     val data = allMockDataWithSuspend()
}

this function call will suspend and once the data is available from DB, the callback(implicit) will be invoked and it will store the response in data field. kotlin coroutine implementation make it seem like a plain old sequential function call.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Doug Stevenson
Solution 2 J.Grbo
Solution 3 ferraro
Solution 4 mightyWOZ