Как реализовать debounce для поиска с корутинами в Kotlin
Что проверяют
Знаете ли вы Flow-операторы и умеете ли привязать корутины к жизненному циклу, не утекая через GlobalScope. Следом почти всегда спрашивают, как отменить запрос, который ещё выполняется, когда пользователь продолжил печатать.
Как отвечать
Правильный каркас живёт во ViewModel и строится на Flow, а не на ручных Job:
- источник:
private val query = MutableStateFlow(""), экран вызываетonQueryChanged(text), который пишет вquery.value; debounce(300): значение проходит дальше, только если за 300 мс не пришло новое;distinctUntilChanged()после него: пользователь стёр букву и набрал её снова, запрос тот же, повторять его незачем;filterили отдельная ветка для пустой строки, чтобы очищать выдачу, а не искать по одной букве;flatMapLatest { repository.search(it) }илиmapLatest: новый запрос отменяет предыдущий. Без этого ответ старого запроса может прийти позже нового и затереть выдачу;stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), UiState.Idle): цепочка работает, пока экран подписан, и переживает поворот.
debounce и flatMapLatest в kotlinx.coroutines помечены как preview и experimental API, поэтому понадобится @OptIn.
В UI собирайте через collectAsStateWithLifecycle() в Compose или repeatOnLifecycle(Lifecycle.State.STARTED) во View-системе. Ошибки ловите catch внутри flatMapLatest, иначе одно исключение завершит весь поток и поиск перестанет работать.
Ручной вариант с job?.cancel() и delay() тоже рабочий, если запускать его в viewModelScope. Цепочку на Flow тестируют в runTest, двигая виртуальное время через advanceTimeBy.
Где спотыкаются
Берут GlobalScope.launch. Корутина переживает экран, держит ссылки и продолжает работу после ухода пользователя.
Пропускают через debounce сам текст поля ввода. В Compose состояние TextField должно обновляться сразу, иначе курсор прыгает и символы теряются. Дебаунсится только запрос к сети.
Берут flatMapConcat или обычный map вместо flatMapLatest. Запросы выстраиваются в очередь, и на медленной сети пользователь видит выдачу по давно стёртому тексту.