如何修正带Debounce的API搜索请求?解决重复触发问题
问题原因分析
你遇到的重复API请求问题,核心原因是每次输入文本变更时都会调用getSearchedNotifications,而这个函数内部每次都会启动一个新的协程去订阅_searchText流。输入"acc"三次就会创建3个独立的collect协程,当debounce延迟结束后,每个订阅的流都会收到最新的"acc"值,最终触发3次重复请求。
解决方案
只需要对ViewModel和UI做两处关键修改,确保流的订阅仅执行一次,而非每次输入都重复订阅:
1. 修改ViewModel:初始化时一次性订阅流
将防抖和API请求逻辑移到ViewModel的init块中,确保只启动一次流订阅;同时用combine处理分页参数(如果需要动态调整),避免硬编码。
private val _searchText = MutableStateFlow("") val searchText: StateFlow<String> = _searchText // 用StateFlow管理分页参数,支持动态调整 private val _paginationParams = MutableStateFlow(Pair(5, 0)) @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) init { viewModelScope.launch { // 合并搜索文本和分页参数,统一处理 combine(_searchText, _paginationParams) { text, (take, skip) -> Triple(text, take, skip) } .debounce(2000L) // 2秒防抖 .distinctUntilChanged() // 仅文本或参数变化时触发 .collectLatest { (searched, take, skip) -> val response = if (searched.isEmpty()) { notificationsRepository.getNotifications(take, skip, "") } else { notificationsRepository.getNotifications(take, skip, searched) } when (response) { is Resource.Success -> { _notifications.update { response.data } } is Resource.Error -> { // 这里添加错误处理逻辑 } else -> {} } } } } // 仅负责更新搜索文本 fun updateSearchedText(newText: String) { _searchText.value = newText } // 如需调整分页参数,调用此方法 fun updatePaginationParams(take: Int, skip: Int) { _paginationParams.value = take to skip }
2. 修改UI:仅更新搜索文本
移除onValueChange中对getSearchedNotifications和getAllNotifications的调用,只保留更新搜索文本的逻辑,流会自动处理后续的防抖和请求。
val searchedText by viewModel.searchText.collectAsState() OutlinedTextField( modifier = Modifier .fillMaxWidth() .padding(15.dp), value = searchedText, onValueChange = { newText -> viewModel.updateSearchedText(newText) } )
关键优化点说明
- 单一订阅:流的订阅仅在ViewModel初始化时执行一次,避免多次订阅导致的重复请求。
- collectLatest:如果用户在防抖延迟期间继续输入,未完成的API请求会被自动取消,仅保留最新的一次请求,完全符合防抖的预期行为。
- 参数动态化:用
combine合并搜索文本和分页参数,后续调整分页时只需更新_paginationParams,无需修改流的订阅逻辑。
内容的提问来源于stack exchange,提问作者Low Pre Intern
相关产品推荐
相关产品推荐

