Как реализовать поиск на экране со списком элементов
Что проверяют
Умеете ли вы построить поиск так, чтобы он не тормозил, не терял состояние при повороте и не слал запрос на каждую букву. Интервьюер смотрит на разделение слоёв: где живёт запрос, где фильтрация, как UI получает результат. Следом спрашивают, что изменится, если данные приходят с сервера или их десятки тысяч.
Как отвечать
Современный каркас на Kotlin:
- UI: в Compose это
TextFieldилиSearchBarиз Material 3 иLazyColumnсkeyдля элементов. В классических View этоSearchViewиRecyclerViewсListAdapter, который черезDiffUtilсчитает разницу списков в фоне вместоnotifyDataSetChanged(); - ViewModel: строка запроса в
MutableStateFlow, результат собирается цепочкойquery.debounce(300).distinctUntilChanged().flatMapLatest { repository.search(it) }.stateIn(...).debounceне даёт искать на каждый символ,flatMapLatestотменяет устаревший поиск, когда пришла новая буква; - источник данных: небольшой список в памяти фильтруется через
filter { it.name.contains(query, ignoreCase = true) }наDispatchers.Default. Локальная база: запрос в Room сLIKEили FTS-таблица через@Fts4. Сервер: тот жеflatMapLatestплюс Paging 3, если выдача большая; - состояние: запрос хранится во ViewModel или в
SavedStateHandle, тогда он переживает и поворот, и смерть процесса.
Стоит проговорить состояния экрана: пустой запрос, загрузка, «ничего не найдено», ошибка сети. Их удобно описать sealed-интерфейсом SearchUiState, который экран просто отрисовывает.
Где спотыкаются
Фильтруют прямо в Activity на главном потоке и зовут notifyDataSetChanged(). На длинном списке это пропуск кадров, потеря анимаций и сброс позиции прокрутки.
Не ставят debounce и не отменяют старые запросы. Ответ на «ко» может прийти позже ответа на «кот», и на экране окажется выдача для устаревшего запроса.
Держат запрос только в поле ввода. После поворота текст на месте, а список показывает всё подряд, потому что состояние фильтра жило во вью, а не во ViewModel.