嵌套监听器中Firebase实时数据库数据获取顺序异常问题
问题:Firebase Realtime Database 排序后异步查询导致顺序错乱
我从Firebase Realtime Database按date子节点排序获取视频数据,之后查询每个视频发布者的订阅状态,但返回结果顺序不符合预期——同一发布者的内容会被集中输出,无法保持原有的按日期排序的顺序。
相关代码
val ref = database.getReference("/content/videos").orderByChild("date") ref.addListenerForSingleValueEvent(object: ValueEventListener { override fun onDataChange(p0: DataSnapshot) { p0.children.forEach { val video = it.getValue(Video::class.java) ?: return println("print1: ${video.video_title}") // Print 1 val user = database.getReference("users/${currentUser?.uid}/subscriptions/${video.publisher_uid}") user.addListenerForSingleValueEvent(object : ValueEventListener{ override fun onDataChange(p0: DataSnapshot) { val sub = p0.getValue(Subscription::class.java) ?: return if (sub.subscribed == true){ println("print2: ${video.video_title}") // Print 2 adapter.add(VideoItem(video)) } } override fun onCancelled(error: DatabaseError) { } }) } } } } override fun onCancelled(p0: DatabaseError) { } })
当前输出
I/System.out: print1: Video1 // publisher1 I/System.out: print1: Video2 // publisher2 I/System.out: print1: Video3 // publisher1 ---------------------------- I/System.out: print2: Video1 // publisher1 I/System.out: print2: Video3 // publisher1 I/System.out: print2: Video2 // publisher2
期望输出
I/System.out: print1: Video1 // publisher1 I/System.out: print1: Video2 // publisher2 I/System.out: print1: Video3 // publisher1 ---------------------------- I/System.out: print2: Video1 // publisher1 I/System.out: print2: Video2 // publisher2 I/System.out: print2: Video3 // publisher1
原因分析
问题核心是异步请求的不确定性:遍历排序后的视频时,每个视频都发起独立的订阅状态查询,这些请求的完成时间不固定(比如同一发布者的请求可能复用缓存更快完成),最终打乱了原有的排序顺序。
解决方法
方法1:批量获取订阅状态,本地匹配(推荐)
先一次性拉取当前用户的所有订阅关系,再在本地匹配视频发布者,完全保留原排序:
val videosRef = database.getReference("/content/videos").orderByChild("date") val subscriptionsRef = database.getReference("users/${currentUser?.uid}/subscriptions") // 先获取所有订阅状态,转成Map方便快速查找 subscriptionsRef.addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(subSnap: DataSnapshot) { val subscribedPublishers = hashMapOf<String, Boolean>() subSnap.children.forEach { val sub = it.getValue(Subscription::class.java) subscribedPublishers[it.key!!] = sub?.subscribed ?: false } // 再获取排序后的视频,本地判断订阅状态 videosRef.addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(videoSnap: DataSnapshot) { videoSnap.children.forEach { val video = it.getValue(Video::class.java) ?: return@forEach println("print1: ${video.video_title}") if (subscribedPublishers[video.publisher_uid] == true) { println("print2: ${video.video_title}") adapter.add(VideoItem(video)) } } } override fun onCancelled(error: DatabaseError) {} }) } override fun onCancelled(error: DatabaseError) {} })
方法2:用Kotlin协程同步处理请求
如果项目使用协程,可通过await()将异步请求转为同步执行,保证顺序:
// 在协程作用域内执行(如viewModelScope、lifecycleScope) lifecycleScope.launch { val videosRef = database.getReference("/content/videos").orderByChild("date") val videoSnap = videosRef.get().await() videoSnap.children.forEach { val video = it.getValue(Video::class.java) ?: return@forEach println("print1: ${video.video_title}") val subRef = database.getReference("users/${currentUser?.uid}/subscriptions/${video.publisher_uid}") val subSnap = subRef.get().await() val sub = subSnap.getValue(Subscription::class.java) if (sub?.subscribed == true) { println("print2: ${video.video_title}") adapter.add(VideoItem(video)) } } }
方法3:递归按顺序处理异步请求
保留逐个请求的逻辑,但通过递归确保严格按原视频顺序处理:
val videosRef = database.getReference("/content/videos").orderByChild("date") videosRef.addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(videoSnap: DataSnapshot) { val sortedVideos = mutableListOf<Video>() // 先把排序后的视频存入列表 videoSnap.children.forEach { val video = it.getValue(Video::class.java) ?: return@forEach sortedVideos.add(video) println("print1: ${video.video_title}") } // 按列表顺序逐个处理订阅查询 processNextVideo(sortedVideos, 0) } override fun onCancelled(error: DatabaseError) {} }) // 递归处理下一个视频 private fun processNextVideo(videoList: List<Video>, currentIndex: Int) { if (currentIndex >= videoList.size) return val video = videoList[currentIndex] val subRef = database.getReference("users/${currentUser?.uid}/subscriptions/${video.publisher_uid}") subRef.addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(subSnap: DataSnapshot) { val sub = subSnap.getValue(Subscription::class.java) if (sub?.subscribed == true) { println("print2: ${video.video_title}") adapter.add(VideoItem(video)) } // 处理下一个视频 processNextVideo(videoList, currentIndex + 1) } override fun onCancelled(error: DatabaseError) { // 即使请求失败,也要继续处理后续视频 processNextVideo(videoList, currentIndex + 1) } }) }
内容的提问来源于stack exchange,提问作者Andrey
相关产品推荐
相关产品推荐

