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

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的数据过滤实现方案?


解决方案

问题分析

加载状态无法自动触发的核心原因有两点:

  1. MutableStateFlow默认不会在订阅时主动发射初始值,除非订阅逻辑明确处理;
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 19:10:20