'Room Dao LiveData as return type causing compile time error

I am using Room and implemented Dao that returns LiveData. It was working fine with below dependency added.

implementation "androidx.room:room-runtime:2.1.0-alpha04"
kapt "androidx.room:room-compiler:2.1.0-alpha04"

But when I added new Room coroutine dependency as mentioned below.

implementation "androidx.room:room-runtime:2.1.0-alpha04"
implementation "androidx.room:room-coroutines:2.1.0-alpha04"
kapt "androidx.room:room-compiler:2.1.0-alpha04"

Below is code which compiles

@Dao
interface AccountDao{

    @Query("SELECT * FROM account_master")
    suspend fun getAllAccounts(): List<Account>
}

Below is the code which gives error.

@Dao
interface AccountDao{

    @Query("SELECT * FROM account_master")
    suspend fun getAllAccounts(): LiveData<List<Account>>
}

started to receive error.

PlayGround/app/build/tmp/kapt3/stubs/debug/com/playground/www/x/datasource/dao/AccountDao.java:11: error: Not sure how to convert a Cursor to this method's return type (androidx.lifecycle.LiveData<java.util.List<com.playground.www.x.datasource.entity.Account>>).
public abstract java.lang.Object getAllAccounts(@org.jetbrains.annotations.NotNull()

Any one facing similar issue?



Solution 1:[1]

I think the solution here is actually to just return the LiveData without using Coroutines. LiveData works out of the box, there's no reason to use Coroutines when returning LiveData.

When using LiveData it already handles it on a background thread. When NOT using LiveData then in that case you can use Coroutines (and maybe eventually Coroutines Channels) or RxJava2.

See this codelab for an example: https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin. Here they need a background thread for inserts but not for the returned LiveData.

Note: there seems to be a mistake in the actual codelab where the DAO is not returning a LiveData. I've corrected that in the sample below.

@Dao
interface WordDao {

    @Query("SELECT * from word_table ORDER BY word ASC")
    fun getAllWords(): LiveData<List<Word>>

    @Insert
    suspend fun insert(word: Word)

    @Query("DELETE FROM word_table")
    fun deleteAll()
}

class WordRepository(private val wordDao: WordDao) {

    val allWords: LiveData<List<Word>> = wordDao.getAllWords()

    @WorkerThread
    suspend fun insert(word: Word) {
        wordDao.insert(word)
    }
}

Solution 2:[2]

Remove the suspend function. LiveData is already asynchronous. No need for a suspend function.

@Dao
interface AccountDao{

    @Query("SELECT * FROM account_master")
    fun getAllAccounts(): LiveData<List<Account>>
}

Solution 3:[3]

Current implementation of Room doesn't support coroutines with LiveData (we can't define a function to be suspend and return LiveData at the same time). As a workaround you can implement it like the following:

@Dao
interface AccountDao {

@Query("SELECT * FROM account_master")
    suspend fun getAllAccounts(): List<Account>
}

class AccountRepository(private val dao: AccountDao) {

    suspend fun getAccounts(): List<Account> {
         return dao.getAllAccounts()
    }
}

And in your implementation of ViewModel class you can create LiveData object and assign a value to it, retrieved from DB:

class MainViewModel(private val accountRepository: AccountRepository) : ViewModel() {

    private val _accounts: MutableLiveData<List<Account>>
    val accounts = _accounts // To access it from Activity/Fragment

    fun loadAccounts() {
        viewModelScope.launch {
            accounts.value = accountRepository.getAccounts()
        }
    }
}

To use Dispatchers.Main import:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

Or you can directly return LiveData without marking the function as suspend:

@Dao
interface AccountDao {

    @Query("SELECT * FROM account_master")
    fun getAllAccounts(): LiveData<List<Account>>
}


class AccountRepository(private val dao: AccountDao) {

    fun getAccounts(): LiveData<List<Account>> {
         return dao.getAllAccounts()
    }
}

class MainViewModel(private val accountRepository: AccountRepository) : ViewModel() {

    val accounts = accountRepository.getAccounts()

}

Solution 4:[4]

As Michael Vescovo pointed out, there are two possible ways of achieving async call:

@Dao
interface AccountDao{
    @Query("SELECT * FROM account_master")
    suspend fun getAllAccounts(): List<Account>
}

@Dao
interface AccountDao{
    @Query("SELECT * FROM account_master")
    fun getAllAccounts(): LiveData<List<Account>>
}

Which one you'll use depends on your use case. For example, I would use the first one if my DAO consumer (usually repository) will create LiveData for the model (in the case that I don't need to observe changes from local DB).

If I need to observe changes in local DB (for example, some other service can update DB in the meantime) I would use the second one.

Solution 5:[5]

Not same, but similar. RoomWordSample CodeLab worked fine until I updated Android Studio and then Gradle plugins.

The failing code is below from the WordDao class. Flow was not recognized.

@Query("SELECT * FROM word_table ORDER BY word ASC")
fun getAlphabetizedWords(): Flow<List<Word>>

I had used the following import, which worked before, but was failing after the updates.

import kotlinx.util.concurrent.Flow

I had to change the import statement as follows:

import kotlinx.coroutines.flow.Flow

I also made quite a few dependency changes too, but Coroutine change was likely needed for this fix.

ext {
    activityVersion = '1.4.0'
    appCompatVersion = '1.4.0'
    lifecycleVersion = '2.4.0'
    coroutines = '1.5.2'
    roomVersion = '2.3.0'
}

dependencies {

implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion"
implementation "androidx.activity:activity-ktx:$activityVersion"

// Dependencies for working with Architecture components
// You'll probably have to update the version numbers in build.gradle (Project)

// Room components
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.room:room-common:2.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'androidx.room:room-ktx:2.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

// Lifecycle components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$rootProject.lifecycleVersion"

// Kotlin components
//implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.0'
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"

}

Another failure was due to missing @Dao annotation.

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
Solution 2 mitch
Solution 3
Solution 4 daneejela
Solution 5 Saleem