Room Coroutine is executed in main thread

1.7k views Asked by At

I am playing around with Kotlin Flow, Coroutines and Room. I am trying to insert data into my Room Database from my Repository.

When inserting a single item there is no problem but if I try to insert a list of items, the execution aborts and there is following message logged to the console:

I/Choreographer: Skipped 47 frames!  The application may be doing too much work on its main thread.

I don't understand why the insert operation is executed on the main thread because according to the docs, a suspended DAO operation should always be executed in a Coroutine initiated internally by Room (which ultimately should run on a background thread). I also tried to run the insert call in another Scope explicitly (withContext(Dispatchers.IO) { ... }) but there is no difference.

Here is what my code looks like:

ViewModel:

fun setStateEvent(stateEvent: StateEvent) {
    viewModelScope.launch {
        when (stateEvent) {
            is StateEvent.GetItems -> {
                repository.getItems().onEach { dateState ->
                    _dataState.value = dateState
                }.launchIn(viewModelScope)
            }
        }
    }
}

Repository:

suspend fun getItems(): Flow<DataState<List<Item>>> = flow {
    emit(DataState.Loading)
    try {
        val items = itemService.getAllItems()
        emit(DataState.Success(items))
        itemDao.insertItems(items) // The execution stops here
    } catch (e: Exception) {
        emit(DataState.Error(e))
    }
}

DAO:

@Insert
suspend fun insertItems(items: List<Item>)

I also tried to debug and find the source of the problem but I had no luck. Would be glad if anyone could tell me what's wrong.

3

There are 3 answers

1
Nicolas On BEST ANSWER

From what I see, there is no problem here. Choreographer messages can be misleading, it might have nothing to do with the main thread. I often see this when debugging, probably due to the debugger overhead. Inflation of heavy layouts can also cause it.

Also you don't need the launchIn(viewModelScope) because the flow is already collected in that scope. Just use collect(). And unlike what the other answers may suggest, you are right in saying the Room automatically switches the thread when using suspend DAO methods.

0
whatever38 On

Sorry guys... There is no problem with the code from my original post. I didn't evaluate the error I threw when catching the exception. So the problem was something completely different (NOT NULL constraint failed).

I didn't think about this because of the message with the main thread.

Thank you all for your help.

3
Gastón Saillén On

You can just add the Dispatchers.IO in the launch of the CoroutineScope that executes the viewmodelScope

fun setStateEvent(stateEvent: StateEvent) {
    viewModelScope.launch(Dispatchers.IO) {
        when (stateEvent) {
            is StateEvent.GetItems -> {
                repository.getItems().collect {
                for(item in it) { _dataState.value = item }
               }
            }
        }
    }
}

Make sure that you have the dependency of room-ktx in your gradle, this will allow you to use suspend functions inside your interface with room

implementation "androidx.room:room-ktx:2.2.5"

See: https://developer.android.com/jetpack/androidx/releases/room#declaring_dependencies

Either way you won't be able to benefit from suspend in your room interface.