Search code examples
androidkotlinandroid-roomkotlin-coroutines

Is it necessary to define the functions inside the DAO as suspend?


I'm creating a simple app using Room. I have created a DAO interface:

interface ItemDao {
    @Insert(onConflict = IGNORE)
    fun addItem(item: Item)
}

And a repository interface:

interface ItemRepository {
    fun addItem(item: Item)
}

Here is the implementation of the function:

class ItemRepositoryImpl(
    private val itemDao: ItemDao
) : ItemRepository {
    override fun addItem(item: Item) = itemDao.addItem(item)
}

And inside the ViewModel class I launch a coroutine using viewModelScope:

class ItemViewModel @Inject constructor(
    private val repo: ItemRepository
) : ViewModel() {
    fun addItem(item: Item) = viewModelScope.launch(Dispatchers.IO) {
        repo.addItem(item)
    }
}

When I call addItem from the UI, the item is successfully added to Room. Now the question is, is it mandatory to set the addItem function inside the ItemDao interface as a suspend function? If yes, why? I'm really confused.

Edit:

Here is how I create an instance of my db:

fun provideItemDb(
    @ApplicationContext
    context : Context
) = Room.databaseBuilder(
    context,
    ItemDb::class.java,
    "item_table"
).build()

Solution

  • Suspend functions are not required, but if you don't mark the functions as suspend, then they block, so they are not safe to call on the main thread.

    If you are using coroutines, it is safe to call a blocking IO function, but only if your current CoroutineContext is not using Dispatchers.Main. You are currently getting away with not using suspend functions because you are running your coroutine with Dispatchers.IO, so the function is called off of the main thread.

    If you are using coroutines, for code simplicity you should always mark them suspend, so the functions are easier and more versatile to use. Imagine if, for example, you wanted to also update a LiveData with the row ID of the newly added item. (You can do this by returning a Long in the DAO function.)

    If you use suspend in your DAO, your function could be:

    fun addItem(item: Item) = viewModelScope.launch {
        _latestRowLiveData.value = repo.addItem(item)
    }
    

    If you didn't use suspend in your DAO, your function would have to be one of these more convoluted implementations:

    fun addItem(item: Item) = viewModelScope.launch {
        _latestRowLiveData.value = withContext(Dispatchers.IO) { repo.addItem(item) }
    }
    
    // or
    
    fun addItem(item: Item) = viewModelScope.launch(Dispatchers.IO) {
        _latestRowLiveData.postValue(repo.addItem(item))
    }
    
    // or
    
    fun addItem(item: Item) = viewModelScope.launch(Dispatchers.IO) {
        val rowId = repo.addItem(item)
        withContext(Dispatchers.Main) {
            _latestRowLiveData.value = rowId
        }
    }
    

    Now imagine a function that wants to do a series of various actions. The non-suspend version of your DAO would lead to very messy-looking (and therefore bug-prone) coroutines in your ViewModel.