android - 使用协程的 Firebase 实时快照监听器
问题描述
我希望能够在我的 ViewModel 中使用 Kotlin 协程来收听 Firebase DB 中的实时更新。
问题是,每当在集合中创建新消息时,我的应用程序都会冻结并且不会从该状态中恢复。我需要杀死它并重新启动应用程序。
这是它第一次通过,我可以在 UI 上看到之前的消息。SnapshotListener
第二次调用时会出现此问题。
我的observer()
功能
val channel = Channel<List<MessageEntity>>()
firestore.collection(path).addSnapshotListener { data, error ->
if (error != null) {
channel.close(error)
} else {
if (data != null) {
val messages = data.toObjects(MessageEntity::class.java)
//till this point it gets executed^^^^
channel.sendBlocking(messages)
} else {
channel.close(CancellationException("No data received"))
}
}
}
return channel
这就是我想观察消息的方式
launch(Dispatchers.IO) {
val newMessages =
messageRepository
.observer()
.receive()
}
}
在我替换为之后sendBlocking()
,send()
我仍然没有在频道中收到任何新消息。SnapshotListener
边被执行
//channel.sendBlocking(messages) was replaced by code bellow
scope.launch(Dispatchers.IO) {
channel.send(messages)
}
//scope is my viewModel
如何使用 Kotlin 协程观察 firestore/realtime-dbs 中的消息?
解决方案
我有这些扩展函数,所以我可以简单地从查询中获取结果作为流。
Flow 是一个完美的 Kotlin 协程结构。 https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
@ExperimentalCoroutinesApi
fun CollectionReference.getQuerySnapshotFlow(): Flow<QuerySnapshot?> {
return callbackFlow {
val listenerRegistration =
addSnapshotListener { querySnapshot, firebaseFirestoreException ->
if (firebaseFirestoreException != null) {
cancel(
message = "error fetching collection data at path - $path",
cause = firebaseFirestoreException
)
return@addSnapshotListener
}
offer(querySnapshot)
}
awaitClose {
Timber.d("cancelling the listener on collection at path - $path")
listenerRegistration.remove()
}
}
}
@ExperimentalCoroutinesApi
fun <T> CollectionReference.getDataFlow(mapper: (QuerySnapshot?) -> T): Flow<T> {
return getQuerySnapshotFlow()
.map {
return@map mapper(it)
}
}
以下是如何使用上述功能的示例。
@ExperimentalCoroutinesApi
fun getShoppingListItemsFlow(): Flow<List<ShoppingListItem>> {
return FirebaseFirestore.getInstance()
.collection("$COLLECTION_SHOPPING_LIST")
.getDataFlow { querySnapshot ->
querySnapshot?.documents?.map {
getShoppingListItemFromSnapshot(it)
} ?: listOf()
}
}
// Parses the document snapshot to the desired object
fun getShoppingListItemFromSnapshot(documentSnapshot: DocumentSnapshot) : ShoppingListItem {
return documentSnapshot.toObject(ShoppingListItem::class.java)!!
}
在您的 ViewModel 类(或您的 Fragment)中,确保您从正确的范围内调用它,以便在用户离开屏幕时适当地删除侦听器。
viewModelScope.launch {
getShoppingListItemsFlow().collect{
// Show on the view.
}
}
推荐阅读
- c - 无法将我的结构数据拉到 main() 函数(C 语言)
- swift - UITextView 中的值不会随用户输入而更新
- c# - 无法运行 CefSharp 浏览器
- linux - 防火墙是否仅适用于侦听套接字而不是已建立的套接字?
- xcode - 通过 Xcode 发布时:下载的软件组件已损坏,无法使用 Xcode 12.1
- c# - Net Core 3:API 运行时的动态模型验证
- python - 如何在 Python 中将字符串(包含不带逗号分隔符的数字表)转换为数组
- php - 覆盖用 if (!function_exists) 包裹的函数
- postgresql - PostgreSQL:使用 WHILE LOOP 的多行结果
- python - 遍历多个 html 文件并转换为 csv