Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write a "suspend" function in a Room @DAO which returns LiveData? [duplicate]

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?

like image 859
Ankh Avatar asked Jun 14 '19 18:06

Ankh


People also ask

What is suspend function in coroutine?

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.

How do I use LiveData with coroutines?

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.

What is LiveData builder in coroutines?

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.


1 Answers

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
   }
}
like image 125
Sergey Avatar answered Oct 23 '22 15:10

Sergey