Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Roomの監視可能なクエリのカスタマイズとレガシーコードへの適用

 Roomの監視可能なクエリのカスタマイズとレガシーコードへの適用

Qiita Bashで発表したスライドです。
https://increments.connpass.com/event/344818/

shiita0903

March 11, 2025
Tweet

Other Decks in Technology

Transcript

  1. Roomとは エンティティ(テーブル)の定義 @Entity(tableName = "user") data class User( @PrimaryKey val

    id: Int, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "is_premium") val isPremium: Boolean, @ColumnInfo(name = "extra_data") val extraData: String ) 3
  2. Roomとは DAO(Data Access Object)の定義 @Dao interface UserDao { @Insert suspend

    fun insert(user: User) @Query("SELECT name FROM user WHERE is_premium") suspend fun selectPremiumUserNames(): List<String> } 4
  3. Roomとは DBの定義 @Database(entities = [User::class], version = 1) abstract class

    UserDatabase : RoomDatabase() { abstract fun getUserDao(): UserDao } 5
  4. 値の変更をFlowで監視 戻り値の型を Flow でラップするだけでOK @Dao interface UserDao { @Query("SELECT name

    FROM user WHERE is_premium") fun selectPremiumUserNamesFlow(): Flow<List<String>> } 6
  5. 値の変更をFlowで監視 @Test fun flowSample() = runTest { dao.selectPremiumUserNamesFlow().test { assertEquals(emptyList(),

    awaitItem()) dao.insert(User(id = 1, name = "name1", isPremium = true, extraData = "")) assertEquals(listOf("name1"), awaitItem()) dao.insert(User(id = 2, name = "name2", isPremium = false, extraData = "")) assertEquals(listOf("name1"), awaitItem()) dao.insert(User(id = 3, name = "name3", isPremium = true, extraData = "")) assertEquals(listOf("name1", "name3"), awaitItem()) } } https://github.com/cashapp/turbine 7
  6. Flowを返す際の注意事項 テーブルの行が更新されるたびにクエリが実行され、値が変わったか どうかにかかわらず Flow に流れる。 dao.insert(User(id = 1, name =

    "name1", isPremium = true, extraData = "")) assertEquals(listOf("name1"), awaitItem()) dao.insert(User(id = 2, name = "name2", isPremium = false, extraData = "")) assertEquals(listOf("name1"), awaitItem()) 2: https://developer.android.com/training/data-storage/room/async-queries#observable 8
  7. Flowを返す際の注意事項 distinctUntilChanged() で、同じ値が連続で流れることを防止。 @Test fun flowDistinctSample() = runTest { dao.selectPremiumUserNamesFlow().distinctUntilChanged().test

    { assertEquals(emptyList(), awaitItem()) dao.insert(User(id = 1, name = "name1", isPremium = true, extraData = "")) assertEquals(listOf("name1"), awaitItem()) dao.insert(User(id = 2, name = "name2", isPremium = false, extraData = "")) // No data is emitted. dao.insert(User(id = 3, name = "name3", isPremium = true, extraData = "")) assertEquals(listOf("name1", "name3"), awaitItem()) } } 9
  8. 実装 private fun RoomDatabase.createInvalidationEventFlowWithThrottle( tableName: String, throttleTimeout: Duration = 1.seconds

    ): Flow<Unit> = callbackFlow { val observer = object : InvalidationTracker.Observer(tableName) { override fun onInvalidated(tables: Set<String>) { trySendBlocking(Unit) } } invalidationTracker.addObserver(observer) send(Unit) // Initial signal to perform first query. awaitClose { invalidationTracker.removeObserver(observer) } } .flowOn(Dispatchers.IO) 14
  9. 実装 private fun RoomDatabase.createInvalidationEventFlowWithThrottle( ... ): Flow<Unit> = callbackFlow {

    ... } .flowOn(Dispatchers.IO) .throttleLatest(throttleTimeout.inWholeMilliseconds) private fun <T> Flow<T>.throttleLatest(timeoutMillis: Long): Flow<T> = this .conflate() .transform { emit(it) delay(timeoutMillis) } 16
  10. 実装 @Dao abstract class UserDao(private val database: UserDatabase) { @Transaction

    open suspend fun queries(): List<String> { doSomething() return selectPremiumUserNames() } fun selectPremiumUserNamesFlow(): Flow<List<String>> = database .createInvalidationEventFlowWithThrottle(tableName = "user") .map { queries() } .distinctUntilChanged() } 17