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

To consume a query as a Flow, depend on the Coroutines extensions artifact and use the extension method

Room + LiveData と同様に SQLDelight でも簡単にFlow化できます。


val players: Flow<List<HockeyPlayer>> =
  playerQueries.selectAll()
    .asFlow()
    .mapToList()

👉 Coroutines - SQLDelight

当然、クエリーの結果の変化を検知して emit します。


@JvmName("toFlow")
fun <T : Any> Query<T>.asFlow(): Flow<Query<T>> = flow {
  val channel = Channel<Unit>(CONFLATED)
  channel.trySend(Unit)

  val listener = object : Query.Listener {
    override fun queryResultsChanged() {
      channel.trySend(Unit)
    }
  }

  addListener(listener)
  try {
    for (item in channel) {
      emit(this@asFlow)
    }
  } finally {
    removeListener(listener)
  }
}

👉 FlowExtensions.kt#L35-L54

使えます。

👉 SQLDelight で View を使うべし 
👉 Turbine で Kotlin coroutine Flow をテストする | #android ファショ通 


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 は、シンプルで使いやすい高品質なツールやライブラリで定評があります。

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


StateFlow の View への公開

以下のような ViewModel があったとして、バッキングプロパティ部分。

どう書いてますか。


class CounterModel {
  private val _counter = MutableStateFlow(0)

  ??? counter ??? = _counter ???

  fun inc() {
    _counter.update { count -> count + 1 } 
  }
}

以下の登場時の開発の様子を参考に。

Introduce StateFlow
👉 Introduce StateFlow · Issue #1973 · Kotlin/kotlinx.coroutines 

StateFlow は、状態を表す更新可能な値の Flow です。

- StateFlow インターフェイスは、現在の値にアクセスするための読み取り専用で、値の更新を collect するための Flow を実装しています。

- MutabaleStateFlow インターフェースは、値を変更する操作を追加しています。

- MutableStateFlow(x) のコンストラクタ関数が用意されています。この関数は、与えられた初期値を持つ MutableStateFlow の実装を返します。値への高速で非リアクティブなアクセスが必要な場合は StateFlow として、値への更新のリアクティブな表示のみが必要な場合は Flow として、外部に公開することができます。

次のようにまとめることができます。


package kotlinx.coroutines.flow

interface StateFlow<T> : Flow<T> {
  val value: T // always availabe, reading it never fails
}

interface MutableStateFlow<T> : StateFlow<T> {
  override var value: T // can read & write value
}

fun <T> MutableStateFlow(value: T): MutableStateFlow<T> // constructor fun

よって、以下、ありがちな記述。(ないか。)


var counter = _counter // NG


val counter = _counter // NG


val counter: MutableStateFlow<Int> = _counter // NG


val counter: StateFlow<Int> = _counter // NG


val counter get() = _counter // NG


val counter: MutableStateFlow<Int> get() = _counter // NG

開発者の間でも、好き嫌いはあるようですが、以下の2パターンが良さげ。Read Only であることが大事。


val counter: StateFlow<Int> get() = _counter  // OK


val counter = _counter.asStateFlow()  // OK

よって、


class CounterModel {
  private val _counter = MutableStateFlow(0)

  val counter = _counter.asStateFlow()

  fun inc() {
    _counter.update { count -> count + 1 } 
  }
}

最近の言語の仕様は、オフィシャルドキュメントでは分かりずらいポリシーが多くあるように思います。

👉 【MVVM】 ViewModel の_プロパティ記述 hatena-bookmark

👉 【MVVM】 Kotlin Flow で使える5つの利用パターン 
👉 StateFlow は distinctUtilChanged 不要