Kotlin中Flow组合失败:如何修复Code A生成UI State?
问题描述
我尝试模仿官方示例项目的Code B实现UI状态生成,但Code A运行失败。核心差异在于:Code A中fun listAll(eSortBy: ESortBy): Flow<EResult<List<MInfo>>>需要接收MutableStateFlow类型的ESortBy参数,导致必须处理嵌套Flow。当前Code A里_listMInfo是Flow<Flow<EResult<List<MInfo>>>>,在combine()块中调用listMInfo.last()后,后续代码完全不触发。
Code A
private val _audioRecordState= MutableStateFlow(ERecordState.STOPPED) private val _listSortBy = MutableStateFlow(ESortBy.START_PRIORITY) private val _listMInfo = _listSortBy.map { handelMInfo.listAll(it)} // 返回 Flow<Flow<EResult<List<MInfo>>>> val homeUIState: StateFlow<HomeUIState> = combine( _audioRecordState, _listSortBy, _listMInfo ) { audioRecordState, listSortBy ,listMInfo-> log("A: ") val temp= listMInfo.last() log("B: ") // 此处代码完全不执行 when (temp) { is EResult.LOADING -> { HomeUIState(audioRecordState, listSortBy) } is EResult.SUCCESS -> { log("C: "+ temp.data.size) HomeUIState(audioRecordState, listSortBy, temp.data) } is EResult.ERROR -> { HomeUIState(audioRecordState, listSortBy) } } } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(), HomeUIState(audioRecordState = ERecordState.STOPPED) ) data class HomeUIState( val audioRecordState: ERecordState = ERecordState.STOPPED, val listSortBy: ESortBy = ESortBy.START_PRIORITY, val listMInfo: List<MInfo> = listOf<MInfo>() ) fun listAll(eSortBy: ESortBy): Flow<EResult<List<MInfo>>>
Code B
private val _savedFilterType = savedStateHandle.getStateFlow(TASKS_FILTER_SAVED_STATE_KEY, ALL_TASKS) private val _filterUiInfo = _savedFilterType.map { getFilterUiInfo(it) }.distinctUntilChanged() private val _userMessage: MutableStateFlow<Int?> = MutableStateFlow(null) private val _isLoading = MutableStateFlow(false) private val _filteredTasksAsync = combine(taskRepository.getTasksStream(), _savedFilterType) { tasks, type -> filterTasks(tasks, type) } .map { Async.Success(it) } .catch<Async<List<Task>>> { emit(Async.Error(R.string.loading_tasks_error)) } val uiState: StateFlow<TasksUiState> = combine( _filterUiInfo, _isLoading, _userMessage, _filteredTasksAsync ) { filterUiInfo, isLoading, userMessage, tasksAsync -> when (tasksAsync) { Async.Loading -> { TasksUiState(isLoading = true) } is Async.Error -> { TasksUiState(userMessage = tasksAsync.errorMessage) } is Async.Success -> { TasksUiState( items = tasksAsync.data, filteringUiInfo = filterUiInfo, isLoading = isLoading, userMessage = userMessage ) } } } .stateIn( scope = viewModelScope, started = WhileUiSubscribed, initialValue = TasksUiState(isLoading = true) )
修复方案
问题根源是嵌套Flow未正确展平,且误用了last()挂起函数:last()会等待Flow完全结束才返回结果,但listAll返回的Flow通常是持续发射数据的热流/冷流,导致代码阻塞在listMInfo.last(),后续逻辑无法执行。
正确做法是用flatMapLatest展平嵌套Flow,它会在_listSortBy更新时自动取消之前订阅的listAll Flow,转而订阅新排序对应的Flow,完美匹配排序切换的场景。
修改后的Code A代码如下:
private val _audioRecordState= MutableStateFlow(ERecordState.STOPPED) private val _listSortBy = MutableStateFlow(ESortBy.START_PRIORITY) // 用flatMapLatest替换map,展平嵌套Flow private val _listMInfo = _listSortBy.flatMapLatest { handelMInfo.listAll(it) } val homeUIState: StateFlow<HomeUIState> = combine( _audioRecordState, _listSortBy, _listMInfo ) { audioRecordState, listSortBy, listResult -> // 直接处理listResult,无需再处理嵌套Flow when (listResult) { is EResult.LOADING -> { HomeUIState(audioRecordState, listSortBy) } is EResult.SUCCESS -> { log("C: "+ listResult.data.size) HomeUIState(audioRecordState, listSortBy, listResult.data) } is EResult.ERROR -> { HomeUIState(audioRecordState, listSortBy) } } } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(), HomeUIState(audioRecordState = ERecordState.STOPPED) ) data class HomeUIState( val audioRecordState: ERecordState = ERecordState.STOPPED, val listSortBy: ESortBy = ESortBy.START_PRIORITY, val listMInfo: List<MInfo> = listOf<MInfo>() ) fun listAll(eSortBy: ESortBy): Flow<EResult<List<MInfo>>>
补充说明
- 如果需要保留之前排序的Flow直到新Flow发射数据,可改用
flatMapConcat;如果允许同时存在多个Flow并合并结果,用flatMapMerge。但排序场景下flatMapLatest是最优选择,因为旧排序的结果没有保留价值。 - 展平后
_listMInfo的类型变为Flow<EResult<List<MInfo>>>,和其他普通Flow一样可以直接参与combine,无需额外处理嵌套结构。
内容的提问来源于stack exchange,提问作者HelloCW
相关产品推荐
相关产品推荐

