Firebase Realtime Database操作成功但Flow未返回Success问题
正在Firebase仓库层实现removeCategory(categoryAddress: String)函数,用于删除Realtime Database指定节点,计划通过Flow发射三类操作状态:
- Loading:加载中
- Success:删除成功
- Some Error occurred: [error_message]:删除失败,携带具体错误信息
当前异常现象:数据库中指定节点可以被成功删除,但ViewModel层始终无法接收到Success状态,仅能收到初始的Loading状态。
现有问题代码
仓库层实现
override fun removeCategory(categoryAddress: String)= flow { val response = StatusString() realtimeRootRef.child(categoryAddress).ref.removeValue().addOnSuccessListener { response.status = "Success" }.addOnFailureListener{ response.status = "Some Error occurred : ${it.message}" } emit(response) }
ViewModel层调用逻辑
init { viewModelScope.launch { repository.removeCategory("remove_testing").collectLatest { Log.d(TAG,"statusString : ${it.status}") } } }
状态类定义
data class StatusString( var status: String = "Loading" )
Firebase的removeValue()是异步非阻塞操作,你注册的成功/失败监听器会在网络请求完成后才异步触发。但当前代码在注册完监听器后会立刻执行emit(response),此时回调还没执行,response里的status还是初始值Loading;等后续回调触发修改response.status字段时,Flow已经完成了初始值的发射并且执行结束,不会再把修改后的值推送给收集方,自然收不到Success/Error状态。
将Firebase异步Task转换为协程挂起函数,按操作时序依次发射对应状态,不要依赖可变对象的字段修改来传递状态。
- 首先发射Loading状态
- 挂起协程等待删除操作执行完成
- 根据执行结果发射成功/失败状态
推荐使用kotlin协程官方提供的play-services扩展,直接把Firebase的Task转为挂起函数,代码更简洁:
首先添加对应依赖(版本号和你项目使用的协程版本保持一致即可):
// 模块级build.gradle.kts依赖示例 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
修改仓库层实现如下:
override fun removeCategory(categoryAddress: String) = flow { // 先发射加载状态 emit(StatusString("Loading")) try { // 挂起等待删除操作完成,无需手动设置回调 realtimeRootRef.child(categoryAddress).ref.removeValue().await() // 无异常说明删除成功,发射成功状态 emit(StatusString("Success")) } catch (e: Exception) { // 捕获异常说明删除失败,发射错误状态 emit(StatusString("Some Error occurred: ${e.message}")) } }
优化建议:将
StatusString类的status字段从可变var改为不可变val,每次发射状态时创建新的实例,避免共享可变状态引发的不可预期问题:data class StatusString( val status: String = "Loading" )
如果不想引入额外依赖,也可以使用callbackFlow实现相同逻辑,注意在流取消时做好资源清理:
override fun removeCategory(categoryAddress: String) = callbackFlow { // 发射加载状态 trySend(StatusString("Loading")) realtimeRootRef.child(categoryAddress).ref.removeValue() .addOnSuccessListener { trySend(StatusString("Success")) close() } .addOnFailureListener { e -> trySend(StatusString("Some Error occurred: ${e.message}")) close(e) } awaitClose { // 此处可写流取消时的资源释放逻辑,Firebase Task无需手动注销监听器 } }
内容的提问来源于stack exchange,提问作者kotlin_noob

