'It is safe to request dao flow from UI thread

If we declare a db operation with @Query("..") fun itemList(): List<Item>, we must make sure that the code runs on a background thread.

If we declare it as a suspend function @Query("..") suspend fun itemList(): List<Item>, the code generated by room creates a coroutine and posts it via CoroutinesRoom.execute - which is safe to call on the Main thread

But what about the operations declared with a Flow return type? @Query("..")fun itemList(): Flow<List<Item>> Are they safe to be called on Main thread?

My opinion, looking at the source code generated by room, is that it's not very bad to call that function from UI thread. There is some synchronized code that runs on the calling thread - but the db operation is done on a background thread.



Solution 1:[1]

For your case @Query("..")fun itemList(): Flow<List<Item>> room generates flow - it's like observable pattern and you should subscribe to the flow to receive emitted values. You should call to collect function on flow which should be called from coroutines scope and will not blocks main thread(like in your sample with suspend @Query). That's why you should not think about thread just select right scope. Please see sample for android.

Just cold flow object will be created when user call to:

@Query("..")fun observeItemList(): Flow<List<Item>>

on thread from which the method called. To create flow object room generates RoomSQLiteQuery object: just build string representation of statement from @Query("..") and binds query arguments if they are exists(with applying the room's type converters). Then room call CoroutinesRoom.createFlow() method to create a flow object by flow builder . All code of flow builder(bock) will be executed in context of coroutine from which collect method was called.

I wrote a little sample:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow

fun main() {
    println("main thread: ${Thread.currentThread()}") // main thread.
    runBlocking {
        println("runBlocking thread: ${Thread.currentThread()}") // main thread too.
        launch(Dispatchers.Default) {
            println("launch thread create flow: ${Thread.currentThread()}")// DefaultDispatcher thread
            val myFlow = observeItemList()
            launch (newSingleThreadContext("MyOwnThread")){ // declare context of the coroutine
                println("launch collect thread: ${Thread.currentThread()}")// MyOwnThread thread
                myFlow.collect {
                    println("flow collect thread: ${Thread.currentThread()}") // in the context of the calling coroutine (MyOwnThread thread)
                    println("emited value: $it")
                }
            }
        }
        println("some code in main.")
    }
}

fun observeItemList() : Flow<Int> {
    println("call to observeItemList thread: ${Thread.currentThread()}") // DefaultDispatcher thread
    return flow{
        println("flow builder thread: ${Thread.currentThread()}") // MyOwnThread thread
        repeat(3) {
            emit(it)
            delay(1000)
        }
    }
}

Output:

main thread: Thread[main,5,main]
runBlocking thread: Thread[main,5,main]
some code in main.
launch thread create flow: Thread[DefaultDispatcher-worker-1,5,main]
call to observeItemList thread: Thread[DefaultDispatcher-worker-1,5,main]
launch collect thread: Thread[MyOwnThread,5,main]
flow builder thread: Thread[MyOwnThread,5,main]
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 0
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 1
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 2

That why all of code for creating a query statement, arguments binding, creating flow object , collecting emitted items by flow will executed on thread of coroutine context(dispatcher) in which you call:

launch(Right coroutine dispatcher){
// all code works on 'Right coroutine dispatcher'
    dao.observeItemList().collect{ }
}

In this case you just get overhead on coroutine creating and starting.

For example if you call to launchIn from some method:

fun test() {
     dao.observeItemList().forEach{}.launchIn(scope)
}

Then the flow will be created on test() calling thread but flow's builder body and collecting will be executed on scope's dispatcher. In this case you get overhead on coroutine creating, starting and creating flow's object (without executing flow's builder block).

If you what to run whatever on a not main thread you should pay any way - do some work on main thread (I mean prepare some objects , starting task in other thread).

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