Cloud Firestore数据变更后如何实现Jetpack Compose UI重组?
Cloud Firestore数据变更后Jetpack Compose UI未触发重组问题
当Cloud Firestore数据库中的数据发生变更(例如新增条目或修改条目的read值)时,即便已将列表设置为mutableState,Jetpack Compose UI仍不会触发重组。
ViewModel层:从Firebase获取数据
private val _booksFromFB = mutableStateOf(listOf<BookFB>()) val booksFromFB = _booksFromFB init { getBooksFromFB() } fun getBooksFromFB() { viewModelScope.launch { _isLoading.value = true _booksFromFB.value = repository.getBooksFromFB().data!! if (_booksFromFB.value.isNotEmpty()) _isLoading.value = false } }
仓库层:从Firebase获取数据
override suspend fun getBooksFromFB(): Resource<List<BookFB>> { return try { Resource.Loading(true) val response = queryBook.get().await().documents.map { documentSnapshot -> documentSnapshot.toObject(BookFB::class.java)!! } if (response.isNotEmpty()) Resource.Loading(false) Resource.Success(response) } catch (exception: FirebaseFirestoreException) { Resource.Error(exception.message.toString()) } }
UI层:获取并使用列表(LazyColumn内)
val userBooks = viewModel.booksFromFB.value.filter { it.userId == currentUser?.uid.toString() }
userBooks的进一步使用
CurrentlyReadingSection( context = context, navController = navController, userBooks = userBooks.filter { it.read == false }, commonViewModel = commonViewModel )
CurrentlyReadingSection组件
LazyRow() { items(userBooks) { book -> BookRow( modifier = Modifier.padding(4.dp), onItemClicked = { } ) { val isExpanded = remember { mutableStateOf(false) } val isRead = rememberSaveable { mutableStateOf(false) } Box( modifier = Modifier .fillMaxSize() .background(AppColors.mBackgroundSec), contentAlignment = BottomCenter ) { Row { AnimatedVisibility(visible = isExpanded.value) { Box( modifier = Modifier .fillMaxSize() .clickable { isExpanded.value = !isExpanded.value }, contentAlignment = Center ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { MyButton( modifier = Modifier .fillMaxWidth(0.7f) .fillMaxHeight(0.2f) .clip(RoundedCornerShape(12.dp)), text = "READ", fontSize = 12, contentPadding = 8 ) { isRead.value = !isRead.value isExpanded.value = !isExpanded.value viewModel.updateBook(context = context, book = book, isRead = isRead.value) } Spacer(modifier = Modifier.height(4.dp)) MyButton( modifier = Modifier .fillMaxWidth(0.7f) .fillMaxHeight(0.24f) .clip(RoundedCornerShape(12.dp)), text = "RATE", fontSize = 12, contentPadding = 8 ) { commonViewModel.currentBook.value = book navController.navigate(Screen.Rate.route) } } } } AsyncImage( modifier = Modifier .fillMaxSize() .clip(RoundedCornerShape(12.dp)) .clickable { isExpanded.value = !isExpanded.value }, model = ImageRequest.Builder(context) .data( if (isValid(book.image)) book.image else com.example.read.R.drawable.imagenotfound ) .crossfade(true) .build(), contentScale = ContentScale.FillBounds, contentDescription = "Book image" ) } Box( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.1f) .background(if (book.read!!) AppColors.mGreen else AppColors.mRed) ) } } } }
BookRow组件
@Composable fun BookRow( modifier: Modifier = Modifier, book: BookFB = BookFB(), category: MyCategory = MyCategory("", ""), shapeDp: Int = 12, heightSize: Int = 170, widthSize: Int = 120, isForYou: Boolean = false, isYourCollection: Boolean = false, onItemClicked: () -> Unit = {}, content: @Composable() () -> Unit, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = if (isYourCollection) Modifier.widthIn(max = widthSize.dp) else Modifier.fillMaxWidth() ) { Surface( shape = RoundedCornerShape(shapeDp.dp), elevation = 4.dp, modifier = modifier.border(2.dp, color = Color.Black, shape = RoundedCornerShape(shapeDp.dp)) ) { Box( modifier = Modifier .clickable { onItemClicked() } .height(heightSize.dp) .width(widthSize.dp) .background(AppColors.mBackgroundSec)) { content() } } if (isYourCollection) { Text( modifier = Modifier.padding(top = 4.dp), text = book.title.toString(), fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic, fontSize = 14.sp, color = AppColors.mTextWhite, textAlign = TextAlign.Center, maxLines = 3, overflow = TextOverflow.Clip ) } if (isForYou) { Text( modifier = Modifier.padding(top = 4.dp), text = category.name, fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic, fontSize = 18.sp, color = AppColors.mTextWhite, textAlign = TextAlign.Center, maxLines = 3, overflow = TextOverflow.Clip ) } }
首页进度条
if (viewModel.isLoading.value) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator(color = AppColors.mMain) } } else { content.. }
问题原因与解决方案
未监听Firestore实时数据变更
当前代码仅通过get().await()一次性拉取数据,不会感知后续数据库变更。需改用Firestore的addSnapshotListener实时监听功能,数据变化时自动更新ViewModel中的mutableState。updateBook后未同步本地数据
调用viewModel.updateBook后,未更新本地_booksFromFB的值,UI无法感知数据变化。若使用一次性拉取,需在更新完成后重新调用getBooksFromFB();若用实时监听,更新后监听器会自动同步最新数据。优化State暴露方式
将val booksFromFB = _booksFromFB改为val booksFromFB: State<List<BookFB>> = _booksFromFB,确保UI能正确观察State变化。
示例修改:仓库层添加实时监听
// 仓库层新增实时监听方法 fun observeBooksFromFB(onResult: (Resource<List<BookFB>>) -> Unit) { queryBook.addSnapshotListener { snapshot, error -> if (error != null) { onResult(Resource.Error(error.message.toString())) return@addSnapshotListener } snapshot?.let { val response = it.documents.map { doc -> doc.toObject(BookFB::class.java)!! } onResult(Resource.Success(response)) } } } // ViewModel层初始化时启动监听 init { repository.observeBooksFromFB { resource -> when(resource) { is Resource.Success -> { _booksFromFB.value = resource.data!! _isLoading.value = false } is Resource.Error -> { // 处理错误逻辑 _isLoading.value = false } is Resource.Loading -> { _isLoading.value = resource.loading } } } }
修改updateBook逻辑
fun updateBook(context: Context, book: BookFB, isRead: Boolean) { viewModelScope.launch { repository.updateBookReadStatus(book.id, isRead) // 若未使用实时监听,需重新拉取数据刷新UI getBooksFromFB() } }
内容的提问来源于stack exchange,提问作者deja
相关产品推荐
相关产品推荐

