web-dev-qa-db-ja.com

Android RoomKotlin-バックグラウンドスレッドでのクエリ-戻り値の問題

サンプルプログラムをJava/SQLiteからKotlin/Roomに変換しました。

バックグラウンドスレッドで戻り値を使用してクエリを実装するのに苦労しています。

これは尋ねられましたが、私はそれを機能させることができませんでした。私は同様の質問への回答を読みましたが、いくつかは非推奨であるか、いくつかの解決策は些細なことであるために複雑に見えます。

クエリの戻り値を使用する必要があるときに、簡単な解決策を思い付くのに本当に困惑しています。

(allowMainThreadQueries()を使用してメインスレッドでクエリを強制的に実行すると、すべてが正常に機能します)

これは、バックグラウンドスレッドでクエリを実行したい関数の1つです。

fun getCrimes(): List<Crime> {
    val crimes = crimesDAO.getAllCrimes() as ArrayList<Crime>
    return crimes
}

私は次のように関数を呼び出すことができ、それは機能しますが、他のクラス全体に非同期呼び出しを追加する必要があり、エレガントではないようです:

AsyncTask.execute {
    mCrimes = getCrimes() as ArrayList<Crime>
}

==> getCrimes自体を変更して、次のようにバックグラウンドでクエリを実行するようにします:(間違ったコードが続きます)

fun getCrimes(): List<Crime> {
    var crimes: ArrayList<Crime>

    AsyncTask.execute {
        crimes = crimesDAO.getAllCrimes() as ArrayList<Crime>
    }

    return crimes // This is wrong as crimes in not initialized
}

Kotlinコルーチン、Live Data、rxjavaを調べましたが、これを回避する簡単な方法が見つかりませんでした。

背景情報:

これはデータクラスです:

@Entity(tableName = "crimes_table")
class Crime {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name="id")
    var id: Long = 0

    @ColumnInfo(name="uuid")
    @TypeConverters(UUIDConverter::class)
    var mId: UUID = UUID.randomUUID()

    @ColumnInfo(name="title")
    var mTitle: String? = null

    @ColumnInfo(name="date")
    @TypeConverters(DateConverter::class)
    var mDate: Date? = Date()

    @ColumnInfo(name="solved")
    var mSolved: Boolean = false
}

これはDAOです:

@Dao
interface CrimesListDAO {

    @Query("SELECT * FROM crimes_table")
    fun getAllCrimes(): List<Crime>

    @Query("SELECT * FROM crimes_table WHERE uuid = :uuidString LIMIT 1")
    fun getOneCrime(uuidString: String): Crime

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertCrime(crime: Crime)

    @Update(onConflict = OnConflictStrategy.REPLACE)
    fun updateCrime(crime: Crime)

    @Delete
    fun deleteCrime(crime: Crime)
}

これはDatabaseAppクラスです。

@Database(entities = [(Crime::class)], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun crimesListDAO(): CrimesListDAO
}

これが私がデータベースをインスタンス化する方法です:

class ApplicationContextProvider : Application() {
    ...
    companion object {
        var database: AppDatabase? = null
    ...
    }

    override fun onCreate() {
        super.onCreate()
        ApplicationContextProvider.database = Room.databaseBuilder(this, AppDatabase::class.Java, "crimeBase.db").build()
    }
}
4
Will

第一歩

ブロッキング機能をオンにします

fun getCrimes() = crimesDAO.getAllCrimes() as List<Crime>

一時停止中のものに:

suspend fun getCrimes() = withContext(Dispatchers.IO) { 
    crimesDAO.getAllCrimes() as List<Crime>
}

ステップ2

サスペンド可能な関数を呼び出すには、最初にコルーチンを起動する必要があります。

override fun onSomeEvent() {
    (context as CoroutineScope).launch {
        val crimes = getCrimes()
        // work with crimes
    }
}

contextCoroutineScopeにする方法については、 CoroutineScopeに関するドキュメント を参照してください。

8
Marko Topolnik

更新:私の答えは間違っています。 (Markoに感謝)別のバックグラウンドスレッドを開始しますが、それでもUIスレッドをブロックします。したがって、これはUIスレッドで呼び出しを行わないようにルーム保護を回避しますが、目的を無効にします。

以下のコードを使用して、新しいスレッドを生成しているが、とにかく呼び出し元のスレッドをブロックしていることを確認しました。

fun main(args: Array<String>) {
    exampleBlockingDispatcher()
}

suspend fun printlnDelayed(message: String) {
    delay(2000)
    println(message)
}

// Running on another thread but still blocking the main thread
fun exampleBlockingDispatcher(){
    runBlocking(Dispatchers.Default) {
        println("one - from thread ${Thread.currentThread().name}")
        printlnDelayed("two - from thread ${Thread.currentThread().name}")
    }
    // Outside of runBlocking to show that it's running in the blocked main thread
    println("three - from thread ${Thread.currentThread().name}")
    // It still runs only after the runBlocking is fully executed.
}

元の回答:

何時間も経って、私はそれを理解しました(編集:私は望みます)。バックグラウンドスレッドでコルーチンを使用してDAOメソッドを呼び出し、値を返すことができる正しい方法は次のとおりです。

fun getCrimes(): ArrayList<Crime> = runBlocking(Dispatchers.Default) {
    val result = async { crimesDAO.getAllCrimes() }.await()
    return@runBlocking result as ArrayList<Crime>
}

たくさんのコードとチュートリアルを読んでくださいが、コルーチンで私がお勧めするのはこれが断然最高です:

https://resocoder.com/2018/10/06/kotlin-coroutines-tutorial-stable-version-async-await-withcontext-launch/

詳細を把握し、実際の動作を確認/試してみるのに役立つ簡単なコードの例がたくさんあります。

2
Will

私はAsyncTaskにあまり詳しくありませんが、バックグラウンドタスクが実際にデータのフェッチを終了して値を割り当てる前に、変数「crime」を返しているようです。

AsyncTaskがデータのフェッチを完了した後、犯罪(DBクエリの結果)を返す必要があります。 execute()呼び出しの直後にreturnステートメントを配置するため、バックグラウンドが完了する前でも犯罪を返しています。

AsyncTaskを使用する場合は、コールバック(つまり、onPostExecute()、doInBackground()など)を実装する必要があります。 doInBackground()コールバックからDB(この場合は犯罪)から結果データを返すことができます。

私は個人的に、AsyncTaskではなくRoomデータベースでRxJavaを使用することを好みます。これにより、コードがよりクリーンでわかりやすくなります:)

0
WasabiTea

Roomを使用すると、デフォルトでデータベースへのすべてのリクエストはバックグラウンドスレッドで行われる必要があります。コルーチンを使用してみませんか?次のようなもので使用できます。

suspend fun retrieveCrimes(): List<Crime> {
    return async {
        delay(5000)
        1
    }.await()
}
0
lucasb.aquino

私が知っている最善のアプローチは、コルーチンを使用することです。 Yoは次のようなものを使用できます:

//For example fron the activity's onCreate method:
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    launch(UI){
        ....
        val allCrimes = bg{
            crimesDAO.getAllCrimes()
        }.await()
        //do something with allCrimes (as a List<Crime>)
    }
    ....
}
0
Raymond Arteaga