Android Kotlin Flow数据过滤优化方案咨询
Kotlin Flow 多条件数据过滤方案优化问题
我正在研究如何根据特定条件过滤数据。最初通过创建带辅助注入Tag的ViewModel解决,但多条件过滤时效率低下,实现代码如下:
sealed interface TasksState { data class Success(val tasks: Map<TimeType, RelatedTasksMetaDataResult>): TasksState object Loading: TasksState object Error: TasksState } class TasksViewModel @AssistedInject constructor( getTasksFlowUseCase: GetTasksFlowUseCase, relatedTasksUseCase: RelatedTasksUseCase, @Assisted private val selectedTag: Tag ): ViewModel() { private val _state = MutableStateFlow<TasksState>(TasksState.Loading) val state: StateFlow<TasksState> = _state.asStateFlow() init { viewModelScope.launch { tasksUiStream( getTasksFlowUseCase, relatedTasksUseCase, Filter.TagFilter(selectedTag, Grouping.DateGrouping) ).collect { _state.value = it } } } /** * Get all tasks as stream and convert them to UI state */ private fun tasksUiStream( getTasksFlowUseCase: GetTasksFlowUseCase, relatedTasksUseCase: RelatedTasksUseCase, filter: Filter, completed: Boolean = false ): Flow<TasksState> { return getTasksFlowUseCase( TasksRetrievalParameters(filter, completed) ) .map { tasksResult -> when(tasksResult) { is Success -> { val groupedTasks = tasksResult.data val tasks = mutableMapOf<TimeType, RelatedTasksMetaDataResult>() groupedTasks?.forEach{ groupedTask -> /* Make sure the keys of the grouped tasks are instance of TimeType */ val dueDate = groupedTask.key as? TimeType dueDate?.let { tasks[dueDate] = relatedTasksUseCase(groupedTask.value) } } TasksState.Success(tasks.toSortedMap()) } is Loading -> { TasksState.Loading } is Error -> { TasksState.Error } } } } @AssistedFactory interface Factory { fun create(selectedTag: Tag): TasksViewModel } @Suppress("UNCHECKED_CAST") companion object { fun provideFactory( assistedFactory: Factory, selectedTag: Tag ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return assistedFactory.create(selectedTag) as T } } } }
之后尝试改用MutableStateFlow在过滤条件变化时执行过滤,但存在问题:进入页面后界面一直停留在初始加载状态,无法触发数据发射(偶尔正常),需手动修改过滤条件再恢复初始值才生效。实现代码如下:
data class UiState( val filterQuery: FilterQuery = FilterQuery(), val projectsState: ProjectsState = ProjectsState.Loading ) sealed interface ProjectsState { object Loading: ProjectsState data class Success(val projects: List<ProjectResult>): ProjectsState object Error: ProjectsState } @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class ProjectsViewModel @Inject constructor( getAllProjectsFlowUseCase: GetAllProjectsFlowUseCase, getProjectsFlowUseCase: GetProjectsFlowUseCase, private val upsertProjectUseCase: UpsertProjectUseCase ): ViewModel() { private val _filterQuery = MutableStateFlow(FilterQuery()) val state: StateFlow<UiState> = _filterQuery .flatMapLatest { getProjectsFlowUseCase(it) .mapLatest { result -> UiState( filterQuery = it, projectsState = when(result) { is Result.Success -> ProjectsState.Success(result.data ?: emptyList()) is Result.Loading -> ProjectsState.Loading is Result.Error -> ProjectsState.Error } ) } } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), UiState() ) }
请问是否有更优的基于Kotlin Flow的数据过滤实现方案?
解决方案
问题分析
加载状态无法自动触发的核心原因有两点:
MutableStateFlow默认不会在订阅时主动发射初始值,除非订阅逻辑明确处理;getProjectsFlowUseCase(it)如果是冷流,初始订阅时可能未正确触发数据加载,或者仅发射了Result.Loading后没有后续的Success/Error事件。
修复现有方案
先解决当前方案的加载触发问题,再优化多条件过滤逻辑:
1. 确保初始值主动发射
给_filterQuery添加手动初始发射,或在流转换中加入onStart处理:
private val _filterQuery = MutableStateFlow(FilterQuery()) init { // 手动触发初始过滤,确保流启动 viewModelScope.launch { _filterQuery.emit(FilterQuery()) } }
或修改流转换逻辑,确保初始加载状态和数据请求触发:
val state: StateFlow<UiState> = _filterQuery .flatMapLatest { filter -> getProjectsFlowUseCase(filter) .mapLatest { result -> UiState( filterQuery = filter, projectsState = when(result) { is Result.Success -> ProjectsState.Success(result.data ?: emptyList()) is Result.Loading -> ProjectsState.Loading is Result.Error -> ProjectsState.Error } ) } // 流启动时主动发射加载状态,避免界面空白 .onStart { emit(UiState(filterQuery = filter, projectsState = ProjectsState.Loading)) } } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), UiState(filterQuery = FilterQuery(), projectsState = ProjectsState.Loading) )
2. 优化多条件过滤的流结构
对于多条件过滤,推荐将所有过滤条件封装到单一MutableStateFlow中,使用flatMapLatest联动处理,避免重复请求:
// 封装所有过滤条件 data class FilterParams( val tag: Tag? = null, val completed: Boolean = false, val searchKeyword: String = "" ) @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class TasksViewModel @Inject constructor( private val getTasksFlowUseCase: GetTasksFlowUseCase, private val relatedTasksUseCase: RelatedTasksUseCase ): ViewModel() { // 所有过滤条件统一管理 private val _filterParams = MutableStateFlow(FilterParams()) val state: StateFlow<TasksState> = _filterParams .flatMapLatest { params -> getTasksFlowUseCase( TasksRetrievalParameters( filter = params.tag?.let { Filter.TagFilter(it, Grouping.DateGrouping) } ?: Filter.All, completed = params.completed ) ) .map { tasksResult -> when(tasksResult) { is Success -> { val groupedTasks = tasksResult.data ?: emptyMap() // 简化数据转换逻辑 val tasks = groupedTasks.mapNotNull { (key, value) -> (key as? TimeType)?.let { it to relatedTasksUseCase(value) } }.toMap().toSortedMap() TasksState.Success(tasks) } is Loading -> TasksState.Loading is Error -> TasksState.Error } } .onStart { emit(TasksState.Loading) } } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), TasksState.Loading ) // 对外暴露修改过滤条件的方法 fun updateTagFilter(tag: Tag?) { _filterParams.update { it.copy(tag = tag) } } fun updateCompletedFilter(completed: Boolean) { _filterParams.update { it.copy(completed = completed) } } fun updateSearchKeyword(keyword: String) { _filterParams.update { it.copy(searchKeyword = keyword) } } }
关键优化点
- 单一数据源管理过滤条件:所有过滤参数存入一个
MutableStateFlow,避免多流联动的复杂度; - flatMapLatest 防重复请求:过滤条件快速变化时,自动取消旧请求,只处理最新条件;
- onStart 确保加载状态显示:流启动时主动发射
Loading状态,避免界面空白; - 不可变参数更新:使用
update方法修改条件,保证线程安全和状态一致性。
内容的提问来源于stack exchange,提问作者Zacle
相关产品推荐
相关产品推荐

