You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套监听器中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 06:11:16