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?
If you notice the functions closely, they can be used to resume the coroutine with a return value or with an exception if an error had occurred while the function was suspended. This way, a function could be started, paused, and resume with the help of Continuation. We just have to use the suspend keyword.
ViewModel + LiveData You could use a MutableLiveData like so: But, since you will be exposing this result to your view, you can save some typing by using the liveData coroutine builder which launches a coroutine and lets you expose results through an immutable LiveData. You use emit() to send updates to it.
The liveData() builder creates the apps LiveData object by setting up the coroutine code block to be run on the Main thread Dispatcher (because we didn't specify the dispatcher, and that's what the builder uses by default) apps is subscribed to in the Activity, and the coroutine is executed.
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With