Jetpack Compose中Firestore嵌套集合数据获取为空求助
问题分析
你的代码核心问题是异步调用顺序错误:
- 外层
firestore.collection("restaurants").get()是异步操作,在它的addOnSuccessListener中,你遍历每个文档并发起内层details集合的get()请求,但这些内层请求同样是异步的。 - 你在遍历完文档后立即执行
restaurantList.value = tempList.toList(),此时所有内层的addOnSuccessListener还没执行,tempList仍然是空的,因此UI显示"No restaurants found"。
另外还有两个次要问题:
- Kotlin类名应遵循大驼峰命名规范,
restaurantInfo建议改为RestaurantInfo。 - 类型转换存在安全风险:
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 ) } } } } } } } } }
关键说明
- 使用
viewModelScope.launch确保协程在ViewModel生命周期内运行,避免内存泄漏。 await()方法将Firestore的异步Task转换为挂起函数,等待请求完成后再继续执行,确保所有内层数据都被收集到tempList后再更新状态。- 使用
mutableStateListOf替代mutableStateOf(emptyList()),这样列表的增删操作会自动触发UI重组,无需手动替换整个列表。 - 用
item.name.orEmpty()替代item.name.toString(),避免当字段为null时显示"null"字符串。
内容的提问来源于stack exchange,提问作者mjimperial
相关产品推荐
相关产品推荐

