8

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?

CodingBruceLee
  • 657
  • 1
  • 5
  • 19

5 Answers5

14

Have you read the Flow documentation? It explains it there in a fair bit of detail.

Basically (as I understand it, I've not been using them long myself) your suspending function is returning a List, i.e. all the results in one go. If generating that list of results might take some time, you add the suspend keyword to signify that. And then you fetch the list asynchronously inside a coroutine by calling that function.

Flows are different - their whole point is to provide the results at arbitrary times, and potentially without ever stopping! The data items are delivered whenever they're emitted, instead of all at once in a collection.

So when you create a Flow, you're not actually doing any of the work yet. That's why your function isn't a suspend one, it's just creating the object. To actually get the items, you need to call collect on it, and that needs to happen inside a coroutine, because that's where the async stuff is actually happening.

cactustictacs
  • 17,935
  • 2
  • 14
  • 25
3

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.

J.Grbo
  • 435
  • 5
  • 11
2

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.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • you mean whenn i use Flow, Flow automatically generates async function? so that's why i don't need to use suspend funcstion? – CodingBruceLee Aug 12 '21 at 02:31
  • 1
    As you can see from the API documentation I linked to, the collect method on Flow is suspending. There is only a need to suspend when the data is actually needed. Not when the Flow object is created. – Doug Stevenson Aug 12 '21 at 02:34
1

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.

ferraro
  • 322
  • 2
  • 13
0

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.

mightyWOZ
  • 7,946
  • 3
  • 29
  • 46
  • 1
    i read what you wrote several times, first of all, i'm not good at English, so i don't know how i understood it well, and the thing is, Flow and LiveData is called when the data is ready, that's why they don't need to be suspend thing because when datas are ready or emitted? and it's of course async callback. – CodingBruceLee Aug 12 '21 at 05:27