はじめに
Jetpack Compose には React のフックに似た仕組みがあり、その仕組みを利用することで Firestore のサブスクリプション周りの雑多な処理を関数として抜き出すことができます。
実装
次のコードは、messages
コレクションに何らかの変化があったときにメッセージのリストを再生成する関数です。
data class Message(
@DocumentId val id: String = "",
val sender: String = "",
val receiver: String = "",
val content: String = "",
)
@Composable
fun rememberMessages(): List<Message> {
val messages = remember { mutableStateListOf<Message>() }
DisposableEffect(Unit) {
val registration = Firebase.firestore
.collection("messages")
.addSnapshotListener { snapshot, e ->
if (e != null || snapshot == null) {
Log.e("ERROR", "listen:error", e)
return@addSnapshotListener
}
for (dc in snapshot.documentChanges) {
val doc = dc.document.toObject(Message::class.java)
when (dc.type) {
DocumentChange.Type.ADDED -> {
messages.add(doc)
}
DocumentChange.Type.MODIFIED -> {
val index = messages.indexOfFirst { it.id == doc.id }
messages[index] = doc
}
DocumentChange.Type.REMOVED -> {
messages.removeIf { it.id == doc.id }
}
}
}
}
onDispose {
registration.remove()
}
}
return messages.toList()
}
mutableStateListOf
を使うことで、ミュータブルかつ変更の検知可能なリストを作ることができます。このリストの要素を追加、修正、削除したときは再コンポーズが走ります。
addSnapshotListener
により、messages
コレクションの変更内容を知ることができます。もし既に messages
コレクションに10個のドキュメントが存在するとき、ハンドラを登録したタイミングで dc.type == DocumentChange.Type.ADDED
のブロックが10回呼ばれます。よって、たったこれだけのコードでドキュメント一覧の取得とサブスクリプションが完了しています。
呼び出し側では次のように書くだけです。
@Composable
fun MyScreen() {
val messages = rememberMessages()
}
messages
コレクションに何らかの変更が行われたときに MyScreen
の再コンポーズが走ります。
おわりに
まさか、ここまできれいに Firestore のサブスクリプション部分を関数として切り出せるとは思いませんでした。