Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Roomの監視可能なクエリのカスタマイズとレガシーコードへの適用
Search
shiita0903
March 11, 2025
Technology
350
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Roomの監視可能なクエリのカスタマイズとレガシーコードへの適用
Qiita Bashで発表したスライドです。
https://increments.connpass.com/event/344818/
shiita0903
March 11, 2025
More Decks by shiita0903
See All by shiita0903
ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界
shiita0903
2
1.4k
Other Decks in Technology
See All in Technology
どんな手を使っても絶対間に合わせるスケジューラ
asari194617
0
1.5k
AIレビュー時代に必要なのは、SLOで引く撤退ライン
nobuoooo
0
150
hookで自作する Claude Code の「実況ボード」
shinyasaita
2
230
All About Sansan – for New Global Engineers
sansan33
PRO
1
1.5k
GopherCon @シアトル に行ってきました
logica0419
0
390
MCPを待つな、パスキーを拡げよう(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
350
AI駆動開発を組織で促すために
lycorptech_jp
PRO
7
8.7k
500名弱規模の組織のPythonプロジェクト(dbt) をどう管理するか?
hiracky16
0
360
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
3.2k
Eight Engineering Unit 紹介資料
sansan33
PRO
3
8.3k
マイナンバーカード本人確認の実装比較(OAuth/OIDC Numa (Immersion) Workshop 2026) / 20260825 numa-12
oidfj
PRO
0
340
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
12
86k
Featured
See All Featured
Testing 201, or: Great Expectations
jmmastey
46
8.2k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
490
AI: The stuff that nobody shows you
jnunemaker
PRO
9
950
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
GraphQLとの向き合い方2022年版
quramy
50
15k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.4k
Transcript
Roomの監視可能なクエリのカスタマイ ズとレガシーコードへの適用
Room とは SQLiteを扱うためのAndroidXのライブラリ。 特徴 クエリのコンパイル時の検証 コード生成によるボイラープレートの削減 1: https://developer.android.com/training/data-storage/room 2
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
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
Roomとは DBの定義 @Database(entities = [User::class], version = 1) abstract class
UserDatabase : RoomDatabase() { abstract fun getUserDao(): UserDao } 5
値の変更をFlowで監視 戻り値の型を Flow でラップするだけでOK @Dao interface UserDao { @Query("SELECT name
FROM user WHERE is_premium") fun selectPremiumUserNamesFlow(): Flow<List<String>> } 6
値の変更を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
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
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
レガシーコードで起こった問題
レガシーコードで起こった問題1 distinctUntilChanged() では無意味なクエリの実行自体を防ぐことは できない。 短時間での頻繁な更新と時間のかかるクエリの組み合わせが、DBを性 能を低下させる可能性がある。 private fun getExtraDataList(): List<Pair<Int,
String>> = List(size = 1000) { it to "extraData$it" } private suspend fun issue() { getExtraDataList().forEach { (userId, extraData) -> dao.updateExtraData(userId, extraData) } } 11
レガシーコードで起こった問題2 @Transaction を利用したクエリは Flow を返すことができない。 @Transaction suspend fun queries(): List<String>
{ // Flow can't be used for the return value. doSomething() return selectPremiumUserNames() } 12
RoomがDBを監視する仕組み InvalidationTracker がテーブルの更新を検知して通知 SQLiteのTriggerを利用して実装されている publicなので、アプリからも利用可能 13
実装 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
実装 RxJavaの throttleLatest に対応する関数を作成。 3: https://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/core/Observable.html#throttleLatest- long-java.util.concurrent.TimeUnit-io.reactivex.rxjava3.core.Scheduler-boolean- 15
実装 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
実装 @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
まとめ Roomで自動生成される Flow を返す関数の問題点 テーブルが更新されるたびに毎回クエリが実行される @Transaction で実装したクエリでは利用できない InvalidationTracker の利用で、監視可能な関数を自作できる 18