0

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.

whatever38
  • 31
  • 7

3 Answers3

3

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.

Nicolas
  • 6,611
  • 3
  • 29
  • 73
  • But the message does only appear when the `insertItems` function is called. Also I am sure that the execution is somehow interrupted because the items are not getting persisted and also, if I switch the `emit` and the `insertItems` function calls and set a breakpoint on the `emit` statement, the breakpoint is never reached, so the execution must have been interrupted while trying to persist the data. BTW, thank you for the tip with `.collect()`! – whatever38 Oct 09 '20 at 18:44
0

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.

whatever38
  • 31
  • 7
-1

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77
  • `repository.getItems()` is never call when using your suggested code. This can be fixed if `.collect()` is called after `.onEach()`. Nonetheless, then the following error occurs: `java.lang.IllegalStateException: Cannot invoke setValue on a background thread`. The dependency is already added. Why would I not be able to benefit from suspend? – whatever38 Oct 09 '20 at 18:57
  • Inside collect do the dataState.value, I have changed the code, take a look at it – Gastón Saillén Oct 09 '20 at 19:14
  • Same error. _dataState is of type `MutableLiveData` and the setValue method has to be called on the main thread. EDIT: Saw your edit right now, I will look at your code, thanks! EDIT2: This won't make a difference because it would still get called on an IO thread and setValue has to be called from the main thread. – whatever38 Oct 09 '20 at 19:20