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

ViewModel从Firestore获取的数据无法在屏幕显示,求排查原因

问题:Firestore获取的数据无法在Jetpack Compose屏幕显示

以下是导致数据无法显示的核心原因及对应解决方案:


核心问题:异步回调时序错误

你的ViewModel中,listStates.value = ListScreenState.Success(lists = listTD)是在Firestore的addOnSuccessListener外部执行的。Firestore的get()是异步操作,回调还未执行时,listTD仍是空列表,此时更新的状态自然没有数据;而后续回调中添加数据的操作,也不会再触发状态更新。

其他潜在问题

  1. 状态监听方式错误:直接读取viewModel.listStates.value并使用let,无法触发Composable重组,状态更新后UI不会自动刷新。
  2. LazyColumn用法不规范:用lists.forEach { item(...) }会导致性能问题,且列表变化时无法正确触发重组。
  3. 空值未处理:FirebaseAuth.currentUser可能为空,文档字段也可能为空,直接调用toString()会引发空指针异常或显示"null"。
  4. Composable内不当的Log调用:TaskRow中的Log.i会在每次重组时执行,属于无效操作且影响性能。
  5. 冗余状态维护:额外维护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
            )
        }
    }
}

关键修正说明

  1. 异步时序修复:将状态更新移至addOnSuccessListener内部,确保数据加载完成后再更新状态。
  2. 状态监听优化:使用collectAsState()监听ViewModel状态,确保UI随状态变化自动重组。
  3. LazyColumn规范:改用items()方法处理列表项,提升性能并保证列表变化时正确重组。
  4. 空值安全处理:添加用户未登录判断、字段空值过滤、颜色解析异常处理,避免崩溃和无效数据。
  5. 用户体验提升:添加加载状态和错误状态显示,替换硬编码偏移的按钮为标准FloatingActionButton。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 12:44:57