Разработка поиска с автодополнением в мобильном приложении
Поиск с автодополнением — компонент, который пользователь замечает только когда он работает плохо: подсказки появляются с задержкой, не отфильтрованы по релевантности, экранная клавиатура перекрывает результаты, или при быстром вводе прилетает ответ на предыдущий запрос вместо актуального. Реализовать правильно с первого раза сложнее, чем кажется.
Ключевые технические задачи
Дебоунс и отмена запросов. Каждое нажатие клавиши не должно уходить на сервер. Стандартная схема: debounce 250–350 мс + отмена предыдущего запроса при новом вводе. В React Native — useRef для хранения таймера + AbortController для fetch, или cancelToken в axios. На Flutter — StreamTransformer.fromHandlers + switchMap в Dart streams, либо через Debouncer с Timer.cancel(). На Android с корутинами — flow.debounce(300) + flatMapLatest гарантируют, что в полёте всегда только один запрос.
Race conditions. Пользователь набирает «нике», получает запрос для «нике», затем стирает до «ни» и снова добавляет «ке». Если сервер ответил на запрос «ни» позже, чем на «нике», интерфейс покажет нерелевантные подсказки. Решение: присваивать каждому запросу уникальный id и игнорировать ответы с устаревшим id.
Клавиатура и layout. На iOS KeyboardAvoidingView с behavior="padding" работает корректно только если правильно выставлен keyboardVerticalOffset. На Android — android:windowSoftInputMode="adjustResize" в манифесте, иначе список подсказок уйдёт под клавиатуру. В Flutter — Scaffold автоматически обрабатывает resizeToAvoidBottomInset, но если SearchBar в кастомном overlay — нужно слушать MediaQuery.of(context).viewInsets.bottom вручную.
Архитектура компонента
Поисковый экран состоит из трёх состояний: пустой ввод (показываем историю поиска или популярные запросы), идёт загрузка (skeleton или индикатор), результаты получены (список подсказок или «ничего не найдено»).
История поиска — в AsyncStorage (RN) или SharedPreferences (Android) / UserDefaults (iOS). Максимум 10–15 последних запросов, дедуплицируем по тексту, показываем с иконкой часов.
Для подсветки совпадений в тексте подсказки используем split по введённой строке + <Text> с разным стилем на совпадение. Не regexp replace — он ломается на спецсимволах в запросе.
Кейс из практики: маркетплейс, React Native, поиск по 200 000 товаров. Жалобы: «подсказки устаревшие». Анализ показал — ответы приходили с разными задержками (CDN кешировал популярные запросы быстро, редкие — медленно), и без отмены устаревших запросов в интерфейсе мигали случайные результаты. Добавили AbortController + requestId tracking — проблема ушла полностью.
Локальный поиск
Для небольших датасетов (< 5000 записей) поиск можно делать клиентски. На Flutter — пакет fuse_dart (fuzzy search). В RN — fuse.js. На Android — Room FTS4 для SQLite full-text search. Это убирает сетевые задержки и работает offline.
Что входит в работу
- SearchBar с анимацией фокуса и кнопкой очистки
- Список автодополнений с подсветкой совпадения
- История поиска с возможностью удаления
- Дебоунс + отмена устаревших запросов
- Обработка состояний: загрузка, пустой результат, ошибка
- Клавиатурный тип (
search,doneaction на кнопке) - Опционально: voice search через SpeechRecognizer (Android) / SFSpeechRecognizer (iOS)
Сроки
2–3 рабочих дня — базовая реализация с API-автодополнением, историей и обработкой edge cases. Стоимость рассчитывается индивидуально.







