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

Jetpack Compose中Firestore嵌套集合数据获取为空求助

问题分析

你的代码核心问题是异步调用顺序错误:

  • 外层firestore.collection("restaurants").get()是异步操作,在它的addOnSuccessListener中,你遍历每个文档并发起内层details集合的get()请求,但这些内层请求同样是异步的。
  • 你在遍历完文档后立即执行restaurantList.value = tempList.toList(),此时所有内层的addOnSuccessListener还没执行,tempList仍然是空的,因此UI显示"No restaurants found"。

另外还有两个次要问题:

  1. Kotlin类名应遵循大驼峰命名规范,restaurantInfo建议改为RestaurantInfo。
  2. 类型转换存在安全风险:data["distance"] as String?: ""如果字段不存在或类型不匹配会抛出异常,应使用安全转换as?。
解决方案

使用Kotlin协程结合Firestore的协程扩展来处理嵌套异步请求,确保所有内层数据获取完成后再更新状态。

步骤1:添加Firestore协程依赖

确保你的build.gradle(Module级别)中包含Firestore的协程扩展依赖:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3"

步骤2:重构ViewModel代码

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await

// 修正类名大驼峰
data class RestaurantInfo(
    var distance: String? = "",
    var location: String? = "",
    var name: String? = "",
    var picture: String? = "",
    var rating: String? = ""
)

class RestaurantViewModel: ViewModel() {
    private val firestore = FirebaseFirestore.getInstance()

    val restaurantList = mutableStateListOf<RestaurantInfo>() // 用mutableStateListOf更适合动态更新

    init {
        fetchItems()
    }

    private fun fetchItems(){
        viewModelScope.launch { // 在ViewModel的协程作用域中执行
            try {
                val restaurantsSnapshot = firestore.collection("restaurants").get().await()
                val tempList = mutableListOf<RestaurantInfo>()
                
                for (document in restaurantsSnapshot.documents) {
                    val detailsSnapshot = document.reference.collection("details").get().await()
                    for (subDocument in detailsSnapshot.documents) {
                        val data = subDocument.data
                        val restaurantItem = RestaurantInfo(
                            distance = data?.get("distance") as? String ?: "",
                            location = data?.get("location") as? String ?: "",
                            name = data?.get("name") as? String ?: "",
                            picture = data?.get("picture") as? String ?: "",
                            rating = data?.get("rating") as? String ?: ""
                        )
                        tempList.add(restaurantItem)
                    }
                }
                
                restaurantList.clear()
                restaurantList.addAll(tempList)
            } catch (e: Exception) {
                e.printStackTrace()
                // 这里可以添加错误处理逻辑,比如通知UI加载失败
            }
        }
    }
}

步骤3:UI层调整

确保在Composable中正确观察ViewModel的状态:

@Composable
fun Restaurants(
    navController: NavController,
    accountViewModel: AccountViewModel,
    restaurantViewModel: RestaurantViewModel // 传入ViewModel
){
    val userInfo by accountViewModel.itemList
    val restaurantList = restaurantViewModel.restaurantList // 直接获取状态列表

    Scaffold(
        topBar = {
            TopBar(
                title = "Account",
                isRestaurantsScreen = true,
                onBackNavClicked = {navController.navigateUp()},
                userInfo = userInfo.firstOrNull()
            ) },
        bottomBar = { BottomBar(navController) }
    ) { paddingValues->
        if(restaurantList.isEmpty()){
           Text("No restaurants found", modifier=Modifier.padding(paddingValues))
        }else{
            LazyColumn(modifier = Modifier.padding(paddingValues)){
                items(restaurantList){ item->
                    Card(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(10.dp),
                        colors = CardDefaults.cardColors(
                            containerColor = Color.White
                        ),
                        elevation = CardDefaults.cardElevation(
                            defaultElevation = 5.dp
                        ),
                        shape = RoundedCornerShape(10.dp)
                    ) {
                        Row{
                            Image(
                                painter = rememberImagePainter(item.picture),
                                contentDescription = null,
                                modifier = Modifier
                                    .size(100.dp)
                                    .padding(end = 8.dp)
                            )

                            Column(
                                modifier = Modifier.padding(8.dp)
                            ) {
                                Text(
                                    text = item.name.orEmpty(),
                                    fontSize = 15.sp,
                                    fontWeight = FontWeight.Bold,
                                    modifier = Modifier.padding(bottom = 2.dp),
                                    color = Color.Black
                                )

                                Text(
                                    text = item.distance.orEmpty(),
                                    fontSize = 13.sp,
                                    fontWeight = FontWeight.Light
                                )

                                Row {
                                    Icon(
                                        imageVector = Icons.Default.LocationOn,
                                        contentDescription = null,
                                        tint = Color.Green,
                                        modifier = Modifier.padding(end = 5.dp)
                                    )

                                    Text(
                                        text = item.location.orEmpty(),
                                        fontSize = 11.sp,
                                        fontWeight = FontWeight.ExtraLight
                                    )
                                }

                                Row{
                                    Text(
                                        text = item.rating.orEmpty(),
                                        fontSize = 11.sp,
                                        fontWeight = FontWeight.Bold,
                                        color = Color.Green,
                                        modifier = Modifier.padding(end = 5.dp)
                                    )

                                    Icon(
                                        imageVector = Icons.Default.Star,
                                        contentDescription = null
                                    )
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

关键说明

  1. 使用viewModelScope.launch确保协程在ViewModel生命周期内运行,避免内存泄漏。
  2. await()方法将Firestore的异步Task转换为挂起函数,等待请求完成后再继续执行,确保所有内层数据都被收集到tempList后再更新状态。
  3. 使用mutableStateListOf替代mutableStateOf(emptyList()),这样列表的增删操作会自动触发UI重组,无需手动替换整个列表。
  4. 用item.name.orEmpty()替代item.name.toString(),避免当字段为null时显示"null"字符串。

内容的提问来源于stack exchange,提问作者mjimperial

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:30:53