ViewModel从Firestore获取的数据无法在屏幕显示,求排查原因
问题:Firestore获取的数据无法在Jetpack Compose屏幕显示
以下是导致数据无法显示的核心原因及对应解决方案:
核心问题:异步回调时序错误
你的ViewModel中,listStates.value = ListScreenState.Success(lists = listTD)是在Firestore的addOnSuccessListener外部执行的。Firestore的get()是异步操作,回调还未执行时,listTD仍是空列表,此时更新的状态自然没有数据;而后续回调中添加数据的操作,也不会再触发状态更新。
其他潜在问题
- 状态监听方式错误:直接读取
viewModel.listStates.value并使用let,无法触发Composable重组,状态更新后UI不会自动刷新。 - LazyColumn用法不规范:用
lists.forEach { item(...) }会导致性能问题,且列表变化时无法正确触发重组。 - 空值未处理:
FirebaseAuth.currentUser可能为空,文档字段也可能为空,直接调用toString()会引发空指针异常或显示"null"。 - Composable内不当的Log调用:
TaskRow中的Log.i会在每次重组时执行,属于无效操作且影响性能。 - 冗余状态维护:额外维护
MutableStateList属于冗余操作,可直接从ViewModel状态获取数据。
修正后的代码
ViewModel代码
class ListScreenViewModel : BaseViewModel() { val listStates: MutableState<ListScreenState> = mutableStateOf(ListScreenState.Default) fun getDataFromFirebase() { // 处理用户未登录情况 val currentUserUid = FirebaseAuth.getInstance().currentUser?.uid ?: run { listStates.value = ListScreenState.Error("用户未登录") return } val db = FirebaseFirestore.getInstance() db.collection("ListTD") .whereEqualTo("userUID", currentUserUid) .get() .addOnSuccessListener { documents -> val listTD = mutableListOf<ListTD>() for (document in documents) { val des = document.getString("description") val lab = document.getString("label") val col = document.getString("colour") val finDate = document.getString("finishDate") val uUID = document.getString("userUID") val docID = document.id Log.i(docID, "LogID") des?.let { Log.i(it, "des") } // 过滤空值,避免添加无效数据 if (des != null && lab != null && col != null && finDate != null && uUID != null) { val listTDItem = ListTD( description = des, label = lab, colour = col, finishDate = finDate, userUID = uUID ) listTD.add(listTDItem) } } // 在回调内部更新状态,确保数据已加载完成 listStates.value = ListScreenState.Success(lists = listTD) } .addOnFailureListener { e -> // 处理数据加载失败情况 listStates.value = ListScreenState.Error(e.message ?: "获取数据失败") } } } // 补充状态类定义 sealed class ListScreenState { object Default : ListScreenState() data class Success(val lists: List<ListTD>) : ListScreenState() data class Error(val message: String) : ListScreenState() }
Screen代码
@Composable fun ListScreen( navigation: INavigationRouter, viewModel: ListScreenViewModel = getViewModel() ) { // 正确监听状态变化,确保UI随状态更新重组 val listState by viewModel.listStates.collectAsState() // 仅在首次启动时触发数据加载 LaunchedEffect(Unit) { if (listState is ListScreenState.Default) { viewModel.getDataFromFirebase() } } Scaffold( topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.YourToDayLists)) }) }, floatingActionButton = { FloatingActionButton( onClick = { navigation.navigateToAddListScreen() }, containerColor = Color.Black ) { Text(text = stringResource(id = R.string.Add), fontSize = 20.sp, color = Color.White) } } ) { paddingValues -> when (listState) { ListScreenState.Default -> { // 显示加载状态 Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } } is ListScreenState.Success -> { MainScreenContent( paddingValues = paddingValues, lists = (listState as ListScreenState.Success).lists, navigation = navigation ) } is ListScreenState.Error -> { // 显示错误信息 Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(text = (listState as ListScreenState.Error).message, color = Color.Red) } } } } } @Composable fun MainScreenContent( navigation: INavigationRouter, paddingValues: PaddingValues, lists: List<ListTD> ) { Column( modifier = Modifier .padding(start = 30.dp, end = 30.dp) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { LazyColumn( modifier = Modifier .padding(paddingValues) .fillMaxSize() ) { // 使用LazyColumn标准的items方法,提升性能 items(lists, key = { it.description }) { listItem -> TaskRow( list = listItem, onRowClick = { navigation.navigateToDetailScreen( listItem.description, listItem.label, listItem.finishDate, listItem.colour ) }, colorString = listItem.colour ) } } } } @Composable fun TaskRow( list: ListTD, onRowClick: () -> Unit, colorString: String ) { // 处理颜色解析失败的情况,设置默认灰色 val bgColor = try { Color(android.graphics.Color.parseColor(colorString)) } catch (e: IllegalArgumentException) { Color.Gray } Row( modifier = Modifier .clickable(onClick = onRowClick) .fillMaxWidth() .padding(vertical = 8.dp, horizontal = 16.dp) .clip(RoundedCornerShape(5.dp)) .background(bgColor) .padding(16.dp) .wrapContentHeight(), horizontalArrangement = Arrangement.Center ) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start ) { Text(text = list.description, fontSize = 20.sp) Spacer(modifier = Modifier.height(10.dp)) Text(text = list.label, fontSize = 15.sp) Spacer(modifier = Modifier.height(10.dp)) Text( text = stringResource(id = R.string.FinishTo) + list.finishDate, fontSize = 15.sp ) } } }
关键修正说明
- 异步时序修复:将状态更新移至
addOnSuccessListener内部,确保数据加载完成后再更新状态。 - 状态监听优化:使用
collectAsState()监听ViewModel状态,确保UI随状态变化自动重组。 - LazyColumn规范:改用
items()方法处理列表项,提升性能并保证列表变化时正确重组。 - 空值安全处理:添加用户未登录判断、字段空值过滤、颜色解析异常处理,避免崩溃和无效数据。
- 用户体验提升:添加加载状态和错误状态显示,替换硬编码偏移的按钮为标准FloatingActionButton。
内容的提问来源于stack exchange,提问作者Andrew Well
相关产品推荐
相关产品推荐

