我希望能够使用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中观察消息?
答案 0 :(得分:3)
对于Firebase的Firestore数据库,有两种类型的调用。
addOnCompleteListener
addSnapshotListener
有一次请求,库await
提供了org.jetbrains.kotlinx:kotlinx-coroutines-play-services:X.X.X
扩展功能。该函数从addOnCompleteListener
返回结果。
资源
扩展功能awaitRealtime
进行检查,包括验证continuation
的状态,以查看其是否处于isActive
状态。这一点很重要,因为当用户的主要内容供稿通过生命周期事件,手动刷新供稿或从其供稿中删除内容时,将调用该函数。没有此检查,将导致崩溃。
ExtenstionFuction.kt
data class QueryResponse(val packet: QuerySnapshot?, val error: FirebaseFirestoreException?)
suspend fun Query.awaitRealtime() = suspendCancellableCoroutine<QueryResponse> { continuation ->
addSnapshotListener({ value, error ->
if (error == null && continuation.isActive)
continuation.resume(QueryResponse(value, null))
else if (error != null && continuation.isActive)
continuation.resume(QueryResponse(null, error))
})
}
为了处理错误,使用了try
/ catch
模式。
Repository.kt
object ContentRepository {
fun getMainFeedList(isRealtime: Boolean, timeframe: Timestamp) = flow<Lce<PagedListResult>> {
emit(Loading())
val labeledSet = HashSet<String>()
val user = usersDocument.collection(getInstance().currentUser!!.uid)
syncLabeledContent(user, timeframe, labeledSet, SAVE_COLLECTION, this)
getLoggedInNonRealtimeContent(timeframe, labeledSet, this)
}
// Realtime updates with 'awaitRealtime' used
private suspend fun syncLabeledContent(user: CollectionReference, timeframe: Timestamp,
labeledSet: HashSet<String>, collection: String,
lce: FlowCollector<Lce<PagedListResult>>) {
val response = user.document(COLLECTIONS_DOCUMENT)
.collection(collection)
.orderBy(TIMESTAMP, DESCENDING)
.whereGreaterThanOrEqualTo(TIMESTAMP, timeframe)
.awaitRealtime()
if (response.error == null) {
val contentList = response.packet?.documentChanges?.map { doc ->
doc.document.toObject(Content::class.java).also { content ->
labeledSet.add(content.id)
}
}
database.contentDao().insertContentList(contentList)
} else lce.emit(Error(PagedListResult(null,
"Error retrieving user save_collection: ${response.error?.localizedMessage}")))
}
// One time updates with 'await' used
private suspend fun getLoggedInNonRealtimeContent(timeframe: Timestamp,
labeledSet: HashSet<String>,
lce: FlowCollector<Lce<PagedListResult>>) =
try {
database.contentDao().insertContentList(
contentEnCollection.orderBy(TIMESTAMP, DESCENDING)
.whereGreaterThanOrEqualTo(TIMESTAMP, timeframe).get().await()
.documentChanges
?.map { change -> change.document.toObject(Content::class.java) }
?.filter { content -> !labeledSet.contains(content.id) })
lce.emit(Lce.Content(PagedListResult(queryMainContentList(timeframe), "")))
} catch (error: FirebaseFirestoreException) {
lce.emit(Error(PagedListResult(
null,
CONTENT_LOGGED_IN_NON_REALTIME_ERROR + "${error.localizedMessage}")))
}
}
答案 1 :(得分:2)
我具有这些扩展功能,因此我可以简单地从查询中以流的形式获取结果。
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()
}
}
在您的ViewModel类(或您的Fragment)中,请确保您在正确的范围内调用此函数,以便当用户离开屏幕时可以适当地删除侦听器。
viewModelScope.launch {
getShoppingListItemsFlow().collect{
// Show on the view.
}
}
答案 2 :(得分:1)
我最终使用的是Flow,它是协程1.2.0-alpha-2
的一部分
return flowViaChannel { channel ->
firestore.collection(path).addSnapshotListener { data, error ->
if (error != null) {
channel.close(error)
} else {
if (data != null) {
val messages = data.toObjects(MessageEntity::class.java)
channel.sendBlocking(messages)
} else {
channel.close(CancellationException("No data received"))
}
}
}
channel.invokeOnClose {
it?.printStackTrace()
}
}
这就是我在ViewModel中观察到的方式
launch {
messageRepository.observe().collect {
//process
}
}
有关主题https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9
的更多信息答案 3 :(得分:0)
这对我有用:
suspend fun DocumentReference.observe(block: suspend (getNextSnapshot: suspend ()->DocumentSnapshot?)->Unit) {
val channel = Channel<Pair<DocumentSnapshot?, FirebaseFirestoreException?>>(Channel.UNLIMITED)
val listenerRegistration = this.addSnapshotListener { value, error ->
channel.sendBlocking(Pair(value, error))
}
try {
block {
val (value, error) = channel.receive()
if (error != null) {
throw error
}
value
}
}
finally {
channel.close()
listenerRegistration.remove()
}
}
然后您可以像使用它一样
docRef.observe { getNextSnapshot ->
while (true) {
val value = getNextSnapshot() ?: continue
// do whatever you like with the database snapshot
}
}
如果观察者块抛出错误,或者该块结束,或者您的协程被取消,则侦听器将自动删除。