ContentProvider を Flow 化する方法 - CashApp Cooper

cashapp/cooper


fun ContentResolver.observeQuery(
  uri: Uri,
  projection: Array<String>? = null,
  selection: String? = null,
  selectionArgs: Array<String>? = null,
  sortOrder: String? = null,
  notifyForDescendants: Boolean = false
): Flow<Query> {
  val query = ContentResolverQuery(this, uri, projection, selection, selectionArgs, sortOrder)
  return flow {
    emit(query)


    val channel = Channel<Unit>(CONFLATED)
    val observer = object : ContentObserver(mainThread) {
      override fun onChange(selfChange: Boolean) {
        channel.offer(Unit)
      }
    }


    registerContentObserver(uri, notifyForDescendants, observer)
    try {
      for (item in channel) {
        emit(query)
      }
    } finally {
      unregisterContentObserver(observer)
    }
  }
}

👉 FlowContentResolver.kt#L43-L90
👉 copper/FlowContentResolver.kt at trunk · cashapp/copper

Kotlin coroutines Flow や RxJava Observable を使ったリアクティブクエリ用の ContentProvider のラッパーです。

使用方法


implementation 'app.cash.copper:copper-flow:1.0.0'

ContentResolver で query() を observeQuery() に変更することで、リアクティブ版を実現します。


contentResolver.observeQuery(uri).collect { query ->
  query.run()?.use { cursor ->
    // ...
  }
}

query() とは異なり、observeQuery() は Query オブジェクトを返します。このオブジェクトは、カーソルの基礎となるクエリを実行するために run() を呼び出す必要があります。これにより、値をキャッシュする中間オペレータがリソースをリークすることなく、コンシューマーがカーソルのライフタイム全体にアクセスできるようになります。

cursor を直接処理する代わりに、含まれる値をセマンティックタイプに変換するためのオペレータを提供しています。


contentResolver.observeQuery(uri)
  .mapToOne { cursor ->
    Employee(cursor.getString(0), cursor.getString(1))
  }
  .collect {
    println(it)
  }


Employee(id=bob, name=Bob Bobberson)

mapToOne オペレータは、1 つの行を返すクエリを受け取り、ラムダを起動してカーソルを希望の型にマッピングします。クエリがゼロまたは1行を返す場合は、コルーチン成果物には mapToOneOrNull オペレータがあり、RxJava成果物には mapToOptional 演算子があります。

クエリがリストを返す場合は、同じラムダでmapToListを呼び出します。


contentResolver.observeQuery(uri)
  .mapToList { cursor ->
    Employee(cursor.getString(0), cursor.getString(1))
  }
  .collect {
    println(it)
  }


[Employee(id=alice, name=Alice Alison), Employee(id=bob, name=Bob Bobberson)]

安定の神Jake産です。

👉 【SQLDelight 】Query を Flow 化するプラグイン 


通知が来ない「通知の自動調整」はOFFに。

「AIが判別」ということにして挙動の基準がユーザーにわからない機能って問題になることが多いです。

👉 LINEやGmailの通知が来ない慢性トラブルの原因がようやく判明?「通知の自動調整」の模様 | スマホダイジェスト 

しかも、機能をOFFにする設定が深く遠い。


設定

 ↓

アプリと通知

 ↓

特別なアプリアクセス

 ↓

通知の自動調整

 ↓

なし

notification-off

おせっかいな機能うざいですね。

Androidの通知機能は、これまでも混乱を招くことが多く、最も分かりづらい機能といってもいいでしょう。

👉 消した通知「通知履歴」をすばやく見る方法【Android11】 
👉 通知が来ない、遅れるときに見直すべき「電池(バッテリー)の最適化」 
👉 【Android Pie】「通知」設定のシンプルな考え方 


Turbine で Kotlin coroutine Flow をテストする

👉 cashapp/turbine: A small testing library for kotlinx.coroutines Flow 

Turbine は Kotlin Flow 向けのテストライブラリです。


flowOf("one", "two").test {
  assertEquals("one", awaitItem())
  assertEquals("two", awaitItem())
  awaitComplete()
}

タービン (Turbine) とは、流体 (Flow)がもっているエネルギーを有用な機械的動力に変換する回転式の原動機の総称。

👉 タービン - Wikipedia 

GitHub にあるサンプルコードを試してみます。

Flow を使っていれば意味は分かるでしょう。



kotlin coroutine flow turbine

@JakeWharton は、シンプルで使いやすい高品質なツールやライブラリで定評があります。

ネーミングは内容に忠実なだけでなく、センスもあります。