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
会社紹介資料 / Sansan Company Profile
sansan33
PRO
24
430k
Flutter × BLE Centralを自前Pluginで実装する設計パターン - MethodChannel / EventChannelで作る双方向ブリッジの実践 / Building Custom Flutter BLE Central Plugins: Bidirectional Bridging with Method & Event Channels
bitkey
PRO
0
240
AI・HPC開発を支えるGPU環境の新しい選択肢 液冷GPUシステム「AquSys」の取り組み
gpuunite_official
0
230
Adding Right-to-Left support to your web application with CSS logical properties — Lessons from Redmine
vividtone
0
170
NANDでも描画したい!
nichica906
3
590
自宅NWにISR4331を導入してみた話
okaits
0
120
【Aiming】共通基盤なのに「共通化しない」課金・認証基盤「LINK」が選び取ったシングルテナント戦略と運用の秘訣
saikeda
0
160
社内の7割が使うデータ基盤を、 データチーム2人で回すためにやったこと
koh_yoshi
4
1.5k
[potatotips #96] Give Your AI Agent the Flutter Playbook
korodroid
0
150
Kiro Crew入門 - 常駐エージェントの仕組みと使いどころ / Intro to Kiro Crew
k_adachi_01
1
820
AIに持続⼒を与える 判断の⻑期記憶設計
eiei114
1
670
Kiro入門|仕様駆動開発で変わるAI時代の開発スタイル
cmkudo
0
340
Featured
See All Featured
How Software Deployment tools have changed in the past 20 years
geshan
1
34k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
660
Google's AI Overviews - The New Search
badams
0
1.1k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
470
The Limits of Empathy - UXLibs8
cassininazir
1
610
Claude Code のすすめ
schroneko
67
230k
Documentation Writing (for coders)
carmenintech
77
5.5k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
260
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
410
The browser strikes back
jonoalderson
0
1.5k
A designer walks into a library…
pauljervisheath
211
24k
Making Projects Easy
brettharned
120
6.7k
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