I'm trying to convert all methods of my @Dao objects to be suspendable for use from Kotlin coroutines.
When I add the suspend modifier to working function definitions and try to Build in Android Studio it fails to generate the XXXXDao_Impl.java class
A very stripped down example:
import androidx.lifecycle.LiveData
import androidx.room.*
@Database(entities = [SomeData::class], version = 1)
abstract class SomeDatabase : RoomDatabase() {
abstract fun getDao(): SomeDataDao
}
@Entity
data class SomeData(@PrimaryKey val id: Int, val name: String)
@Dao
interface SomeDataDao {
@Query("SELECT * FROM SomeData") // Works
fun getAllSync(): List<SomeData>
@Query("SELECT * FROM SomeData") // Works
suspend fun getAllSuspend(): List<SomeData>
@Query("SELECT * FROM SomeData") // Works
fun getAllSyncLiveData(): LiveData<List<SomeData>>
@Query("SELECT * FROM SomeData") // Fails to generate code
suspend fun getAllSuspendLiveData(): LiveData<List<SomeData>>
}
The generated code which is failing:
package com.example.android.rawquerysuspendtest;
import android.database.Cursor;
import androidx.lifecycle.LiveData;
import androidx.room.CoroutinesRoom;
import androidx.room.RoomDatabase;
import androidx.room.RoomSQLiteQuery;
import androidx.room.util.CursorUtil;
import androidx.room.util.DBUtil;
import java.lang.Exception;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.SuppressWarnings;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import kotlin.coroutines.Continuation;
@SuppressWarnings({"unchecked", "deprecation"})
public final class SomeDataDao_Impl implements SomeDataDao {
private final RoomDatabase __db;
public SomeDataDao_Impl(RoomDatabase __db) {
this.__db = __db;
}
@Override
public List<SomeData> getAllSync() {
final String _sql = "SELECT * FROM SomeData";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final List<SomeData> _result = new ArrayList<SomeData>(_cursor.getCount());
while(_cursor.moveToNext()) {
final SomeData _item;
final int _tmpId;
_tmpId = _cursor.getInt(_cursorIndexOfId);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_item = new SomeData(_tmpId,_tmpName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
@Override
public Object getAllSuspend(final Continuation<? super List<SomeData>> p0) {
final String _sql = "SELECT * FROM SomeData";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return CoroutinesRoom.execute(__db, false, new Callable<List<SomeData>>() {
@Override
public List<SomeData> call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final List<SomeData> _result = new ArrayList<SomeData>(_cursor.getCount());
while(_cursor.moveToNext()) {
final SomeData _item;
final int _tmpId;
_tmpId = _cursor.getInt(_cursorIndexOfId);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_item = new SomeData(_tmpId,_tmpName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
}, p0);
}
@Override
public LiveData<List<SomeData>> getAllSyncLiveData() {
final String _sql = "SELECT * FROM SomeData";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return __db.getInvalidationTracker().createLiveData(new String[]{"SomeData"}, false, new Callable<List<SomeData>>() {
@Override
public List<SomeData> call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final List<SomeData> _result = new ArrayList<SomeData>(_cursor.getCount());
while(_cursor.moveToNext()) {
final SomeData _item;
final int _tmpId;
_tmpId = _cursor.getInt(_cursorIndexOfId);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_item = new SomeData(_tmpId,_tmpName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
@Override
protected void finalize() {
_statement.release();
}
});
}
@Override
public Object getAllSuspendLiveData(final Continuation<? super LiveData<List<SomeData>>> p0) {
final String _sql = "SELECT * FROM SomeData";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return CoroutinesRoom.execute(__db, false, new Callable<LiveData<List<SomeData>>>() {
@Override
public LiveData<List<SomeData>> call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false);
try {
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
}, p0);
}
}
The build error message:
/Projects/Android/RawQuerySuspendTest/app/build/tmp/kapt3/stubs/debug/com/example/android/rawquerysuspendtest/SomeDataDao.java:24: error: Not sure how to convert a Cursor to this method's return type (androidx.lifecycle.LiveData<java.util.List<com.example.android.rawquerysuspendtest.SomeData>>).
public abstract java.lang.Object getAllSuspendLiveData(@org.jetbrains.annotations.NotNull()
The build.gradle for the app
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.android.rawquerysuspendtest"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
kapt "androidx.room:room-compiler:2.1.0-rc01"
implementation "androidx.room:room-runtime:2.1.0-rc01"
implementation "androidx.room:room-ktx:2.1.0-rc01"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha01'
}
Am I missing a dependency or annotation processing step?
Current implementation of Room doesn't support coroutines with LiveData
. As a workaround you can implement it like the following:
@Dao
interface AccountDao{
@Query("SELECT * FROM account_master")
suspend fun getAllAccounts(): List<Account>
}
And in your implementation of ViewModel
class you can create LiveData
and assign a value to it, retrieved from DB:
class MainViewModel : ViewModel() {
private val dao: AccountDao = ...// initialize it somehow
private var job: Job = Job()
private val scope = CoroutineScope(job + Dispatchers.Main)
lateinit var accounts: MutableLiveData<List<Account>>
override fun onCleared() {
super.onCleared()
job.cancel()
}
fun getAccounts(): LiveData<List<Account>> {
if (!::accounts.isInitialized) {
accounts = MutableLiveData()
scope.launch {
accounts.postValue(dao.getAllAccounts())
}
}
return accounts
}
}
To use Dispatchers.Main
import:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
If you want to get notified when the changes to the DB occur you can use LiveData<List<Account>>
or Flow<List<Account>>
as a return type of getAllAccounts
method:
@Dao
interface AccountDao{
@Query("SELECT * FROM account_master")
fun getAllAccounts(): Flow<List<Account>>
}
class MainViewModel : ViewModel() {
//...
fun loadAllAccounts(): Flow<List<Account>> = dao.getAllAccounts()
}
// In Activity/Fragment
lifecycleScope.launch {
viewModel.loadAllAccounts().collect { accounts ->
// use accounts, for example populate RecyclerView Adapter
}
}