如何使用Kotlin Flow监听Room变更实时更新Compose UI状态
我开发了一款基于Jetpack Compose的图片应用,用户可查看Room数据库中存储的缓存照片。数据库设有存储布尔值的isFavorite列,用户点击点赞照片时会更新该列的取值。
现有代码实现
DAO层
@Query("SELECT * FROM astrophotoentity") suspend fun getSavedAstroPhotos(): Flow<List<AstroPhotoEntity>> @Query("UPDATE astrophotoentity SET isFavorite =:isFavorite WHERE id=:id") suspend fun updateIsFavoriteStatus(id: String,isFavorite:Boolean)
Repository层
override suspend fun updateIsFavoriteStatus(photo: AstroPhoto, isFavorite:Boolean) { dao.updateIsFavoriteStatus(photo.date,isFavorite) }
UseCase层
class UpdateIsFavoriteStatusUseCase (private val repository: AstroRepository) { suspend operator fun invoke(photo: AstroPhoto, isFavorite:Boolean){ repository.updateIsFavoriteStatus(photo, isFavorite) } }
ViewModel层
页面状态数据类定义:
data class PhotoState( val astroPhotos: List<AstroPhoto> = emptyList(), val isPhotosListLoading: Boolean = false, val errorMessage: String? = null )
ViewModel实现:
@HiltViewModel class OverviewViewModel @Inject constructor( private val useCaseContainer: UseCaseContainer ) : ViewModel() { var state by mutableStateOf(PhotoState()) private set init { // 初始化时将PhotoState的照片列表赋值为数据库查询结果 getPhotosListFromDb() } fun onEvent(event: OverviewEvent) { when(event) { is OverviewEvent.OnMarkFavorite -> { useCaseContainer.updateIsFavoriteStatus(event.photo, event.isFavorite) } is OverviewEvent.OnRemoveFromFavorites -> { useCaseContainer.updateIsFavoriteStatus(event.photo, event.isFavorite) } } } }
Compose UI层
@Composable fun AstroPhotoComposable( photo: AstroPhoto, onMarkAsFavorite: () -> Unit, onRemovePhoto: () -> Unit = {} ) { // 从photo.isFavorite取值并缓存 var isFavorite by remember{ mutableStateOf(photo.isFavorite) } // 其余UI逻辑 }
故障现象
照片的isFavorite状态在true/false之间切换时,变更无法实时反映到UI上。曾尝试将mutableStateOf替换为MutableStateFlow,数据库变更时状态依旧无法更新。目标是实现类似LiveData的数据库变更监听能力,自动获取最新状态同步到UI。
问题出在三个核心位置,按以下步骤修改即可:
修正DAO层Flow查询的修饰符
Room中返回Flow的查询方法不能加suspend修饰符:Flow本身就是异步数据流,加suspend后函数只会返回一次结果,无法持续发射数据库变更后的新数据。将DAO层查询方法修改为:@Query("SELECT * FROM astrophotoentity") fun getSavedAstroPhotos(): Flow<List<AstroPhotoEntity>> // 移除suspend // 更新操作的suspend修饰符保留,写法正确 @Query("UPDATE astrophotoentity SET isFavorite =:isFavorite WHERE id=:id") suspend fun updateIsFavoriteStatus(id: String,isFavorite:Boolean)修正ViewModel的Flow收集逻辑
现有初始化逻辑只获取了一次数据库数据,没有持续收集Flow的更新。首先补充获取照片流的UseCase,在Repository层做好Entity到业务模型AstroPhoto的映射,将流向上暴露到ViewModel。
新增获取照片流的UseCase:class GetSavedAstroPhotosUseCase(private val repository: AstroRepository) { operator fun invoke(): Flow<List<AstroPhoto>> = repository.getSavedAstroPhotos() }然后修改ViewModel初始化逻辑,在
viewModelScope中持续收集流数据,每次数据库变更自动更新页面状态;另外调用挂起函数的更新操作需要包在协程作用域中:@HiltViewModel class OverviewViewModel @Inject constructor( private val useCaseContainer: UseCaseContainer ) : ViewModel() { var state by mutableStateOf(PhotoState()) private set init { viewModelScope.launch { useCaseContainer.getSavedAstroPhotos() .onStart { state = state.copy(isPhotosListLoading = true) } .catch { e -> state = state.copy( isPhotosListLoading = false, errorMessage = e.localizedMessage ) } .collect { photoList -> state = state.copy( astroPhotos = photoList, isPhotosListLoading = false, errorMessage = null ) } } } fun onEvent(event: OverviewEvent) { when(event) { is OverviewEvent.OnMarkFavorite -> { viewModelScope.launch { useCaseContainer.updateIsFavoriteStatus(event.photo, event.isFavorite) } } is OverviewEvent.OnRemoveFromFavorites -> { viewModelScope.launch { useCaseContainer.updateIsFavoriteStatus(event.photo, event.isFavorite) } } } } }移除Composable中冗余的本地状态缓存
remember { mutableStateOf(photo.isFavorite) }只会在组件首次进入组合时初始化一次,后续传入的photo对象更新时,这个本地缓存的状态不会同步变化,相当于锁死了初始值。直接使用参数传入的photo.isFavorite即可,现在ViewModel会在数据库变更后自动更新状态,触发Compose重组拿到最新值:@Composable fun AstroPhotoComposable( photo: AstroPhoto, onMarkAsFavorite: () -> Unit, onRemovePhoto: () -> Unit = {} ) { // 直接使用参数中的值,不需要本地缓存 val isFavorite = photo.isFavorite // 其余UI逻辑保持不变 }
最后确认AstroPhoto是data class,保证Compose和mutableStateOf可以正确识别对象字段变化,触发重组。
内容的提问来源于stack exchange,提问作者Tonnie

