为何CoroutineScope未等待Firebase数据获取完成就执行后续代码?
解决协程异步加载Firebase数据导致ListView显示空列表的问题
核心问题分析
你当前的代码逻辑存在异步操作与UI操作不同步的问题:
- 在
Dispatchers.IO协程中异步拉取Firebase数据时,主线程的UI代码(创建Adapter、设置ListView)会直接执行,此时list还是初始的空列表,自然显示为空。 - 你尝试把UI代码移到协程里报错,是因为UI操作必须在主线程执行,而你用的是
Dispatchers.IO,直接操作View会抛出异常。
代码修复步骤
1. 用生命周期感知协程Scope同步数据与UI
不要手动创建CoroutineScope,改用Android提供的lifecycleScope(Activity中)或viewLifecycleOwner.lifecycleScope(Fragment中),它会自动绑定组件生命周期,避免内存泄漏:
// Activity中直接使用lifecycleScope lifecycleScope.launch { val customList = mutableListOf<CustomClass>() StartPageActivity.customClassExpandList.forEach { if (it.title.contains(CustomClass.title)) { customList.add(getFirebaseData(it.date)) // 收集到12条数据后停止遍历 if (customList.size == 12) return@forEach } } // 切换到主线程更新UI withContext(Dispatchers.Main) { val listAdapter = MyListAdapter(context, customList) listView.adapter = listAdapter listView.visibility = View.VISIBLE listView.dividerHeight = 0 listView.layoutParams.height = calculateHeight(listView, 12) } }
2. 修正getFirebaseData的逻辑错误
你的挂起函数存在变量名错误和回调嵌套问题,导致数据无法正确更新:
suspend fun getFirebaseData(date: LocalDate): CustomClass = withContext(Dispatchers.IO) { val customClass = CustomClass("$date", date, "Empty", false) // 直接用await()获取结果,无需嵌套addOnCompleteListener val document = FirebaseFirestore.getInstance() .collection("users") .document(FirebaseAuth.getInstance().currentUser!!.uid) .collection("customClass") .document(date.toString()) .get() .await() if (document.exists()) { // 修正变量名错误:goal→customClass,修复字段拼写错误:tile→title customClass.description = document["description"].toString() customClass.title = document["title"].toString() } return@withContext customClass }
修复点说明:
- 移除
addOnCompleteListener:await()会直接等待任务完成并返回结果,回调嵌套会导致你修改的变量和返回的customClass不是同一个对象。 - 修正变量名错误:你定义的是
customClass,却错误地写了goal。 - 修复字段拼写错误:把
tile改为title,避免获取不到正确数据。
3. 额外优化建议
- 避免直接访问
StartPageActivity的静态列表,建议通过ViewModel传递数据,符合MVVM架构规范。 - 可以用
async+awaitAll并行拉取Firebase数据,提升加载速度:
// 并行加载示例 val deferredList = StartPageActivity.customClassExpandList .filter { it.title.contains(CustomClass.title) } .take(12) // 直接取前12条,无需遍历判断size .map { async { getFirebaseData(it.date) } } val customList = deferredList.awaitAll()
内容的提问来源于stack exchange,提问作者J. Weidenhagen
相关产品推荐
相关产品推荐

