Firestoreの値をcallbackFlowとasLiveDataを組み合わせて使う

まとめ

  • callbackFlowを使えばFirestoreのリアルタイムアップデートをFlow化できる
  • FlowをasLiveDataする時はgetterじゃなくて代入するようにしないとViewが生きている間はFlowが永遠と生成される

callbackFlow

こんな感じでFireStore上のデータをコールバックからFlowへ変換してあげることができる

class Firestore() {
    fun hasType(type: String): Flow<Boolean> = callbackFlow {
        val doc = Firebase
            .firestore
            .collection("users")
            .document(userId)

        val callback = doc.addSnapshotListener { value, e ->
            // ここでよしなにデータを加工してあげる
            val result = if (e != null) {
                false
            } else {
                val types = value?.documents?.map { it.get("type").toString() }
                types?.contains(type) ?: false
            }
            // offerでFlowにデータを流す
            offer(result)
        }

        // awaitCloseでFlowがキャンセルされた時にコールバックの解放する処理を書く
        awaitClose {
            callback.remove()
        }
    }.distinctUntilChanged()
}

asLiveData

ViewModel側でasLiveData()を使うとLiveDataに変換できる。

このとき、getterで指定すると新しいFlowがgetterにアクセスが有る度に生成されるからどんどんデータが流れてきてしまう。おとなしくインスタンスを代入してあげれば大丈夫。

あと、asLiveDataはcontextを指定しなければLiveDataがInActiveになっても5秒間生き残るのでviewModelScope.coroutineContextを渡しておいたほうが良さそう

class MyViewModel(
  private val firestore: Firestore
) : ViewModel() {
    // こっちはOK
    val isNewType: LiveData<Boolean> = firestore.hasType("newType")
            .asLiveData(
                context = viewModelScope.coroutineContext
            )

   // こっちは無限にFlowがstartしてバンバンとデータが流れてきてしまうので注意!!
   val isNewType: LiveData<Boolean> 
     get() = firestore.hasType("newType")
            .asLiveData(
                context = viewModelScope.coroutineContext
            )
}