Мы реализуем разработку поиска с автодополнением в мобильном приложении под ключ: архитектура запросов, дебаунс, отмена устаревших запросов, кеширование подсказок, история и подсветка совпадений. Задача решается на Swift, Kotlin, Dart и React Native. Из нашей практики: в одном проекте переход на автодополнение снизил время до первого результата с 2,1 секунды до 0,4 секунды — конверсия из поиска выросла на 18%.
Два главных блока боли — race condition и перекрытие клавиатурой. Race condition: ответ на устаревший запрос приходит позже нового и перезаписывает список подсказок. Решение — flatMapLatest (Android) или switchToLatest() (iOS): при новом символе предыдущий запрос отменяется автоматически. Клавиатура перекрывает подсказки, если нижний anchor у списка привязан к экрану, а не к клавиатуре. Мы фиксим это через KeyboardAvoiding (RN) или WindowInsetsCompat (Android), до написания кода проговаривая эту деталь с дизайнером.
Разработка такого поиска охватывает несколько технических этапов: выбор архитектуры, дебаунс, кеш, история и подсветка совпадений. Ниже — конкретная механика каждого с примерами кода.
Как выбрать подход: клиентский или серверный поиск?
Выбор архитектуры поиска определяет срок разработки и поведение в офлайне. Три рабочих варианта:
| Подход | Когда использовать | Стек | Время ответа |
|---|---|---|---|
| Клиентский | Датасет до 5 000 записей | fuse.js (RN), fuse_dart (Flutter), Room FTS4 (Android) | <50 мс |
| Серверный | Большой каталог, персонализация | REST/GraphQL + debounce | 100–400 мс |
| Комбинированный | eCommerce, маркетплейс | Офлайн-кэш + API fallback | <100 мс |
Клиентский поиск через fuse.js быстрее серверного при небольших датасетах и не требует подключения к сети. Комбинированный подход эффективнее для eCommerce: мгновенный ответ из локального кэша, актуализация через API в фоне.
Дебаунс и отмена устаревших запросов
Без дебаунса поиск отправляет запрос на каждый нажатый символ. При скорости набора 3–5 символов в секунду — это 3–5 лишних сетевых обращений. Ответы приходят вразнобой, список подсказок мигает. Debounce 300 мс убирает проблему: запрос уходит только после паузы в наборе.
// iOS: debounce через Combine @Published var query = "" cancellable = $query .debounce(for: .milliseconds(300), scheduler: RunLoop.main) .removeDuplicates() .flatMap { [weak self] q in self?.fetchSuggestions(q) ?? Empty().eraseToAnyPublisher() } .sink { [weak self] in self?.suggestions = $0 } // Android: debounce через Flow + coroutines searchFlow .debounce(300) .distinctUntilChanged() .flatMapLatest { query -> suggestionsUseCase(query) } .collect { suggestions = it } flatMapLatest на Android и аналог на iOS отменяют устаревший запрос автоматически при каждом новом вводе, без ручного управления токенами отмены.
Кеширование подсказок поиска
Результаты популярных запросов кэшируются на устройстве. Мы используем LRU-кэш на 100–200 записей: при повторном вводе ответ приходит мгновенно из памяти, без обращения к API. Экономия трафика — от 40 до 70% для приложений с повторяющимися запросами.
Пример LRU-кэша для подсказок на Kotlin
private val cache = object : LinkedHashMap<String, List<String>>(200, 0.75f, true) { override fun removeEldestEntry(eldest: Map.Entry<String, List<String>>) = size > 200 } fun getSuggestions(query: String): List<String>? = cache[query.lowercase()] fun putSuggestions(query: String, results: List<String>) { cache[query.lowercase()] = results } Для офлайн-доступа расширяем кэш до Room FTS4 на Android или CoreData на iOS — пользователь получает подсказки даже без сети.
История поиска
Храним последние 10–15 запросов в локальном хранилище. Показываем с иконкой часов выше подсказок API, дедуплицируем по тексту. Кнопка очистки истории обязательна: пользователи ожидают её наличия.
| Платформа | Хранилище | Лимит |
|---|---|---|
| iOS | UserDefaults | 15 записей |
| Android | DataStore / SharedPreferences | 15 записей |
| React Native | AsyncStorage | 10 записей |
| Flutter | shared_preferences | 15 записей |
При выборе подсказки из истории сохраняем запрос повторно для обновления порядка, затем запускаем полный поиск по выбранному тексту.
Подсветка совпадений в результатах
Разбиваем текст подсказки на части: совпадающий фрагмент выделяем цветом. На Android — SpannableString с ForegroundColorSpan, на iOS — NSAttributedString, во Flutter — RichText с TextSpan. Подсветка нечувствительна к регистру и учитывает несколько вхождений в одной строке.
// iOS: подсветка совпадений через NSAttributedString func highlight(_ text: String, query: String) -> NSAttributedString { let attr = NSMutableAttributedString(string: text) var searchRange = text.startIndex..<text.endIndex while let range = text.range(of: query, options: .caseInsensitive, range: searchRange) { attr.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: NSRange(range, in: text)) searchRange = range.upperBound..<text.endIndex } return attr } Важно обрабатывать пустую строку запроса: при пустом поле подсказки не показываются или показывается история.
Что входит в разработку поиска?
Мы выполняем полный цикл работ под ключ:
- Архитектурный анализ: выбор подхода (клиентский, серверный, комбинированный)
- Реализация дебаунс-логики и отмены запросов
- Кеширование подсказок поиска: LRU in-memory и персистентный слой
- История поиска с дедупликацией и кнопкой очистки
- Подсветка совпадений на нативных компонентах
- Обработка edge cases: пустой запрос, офлайн, ошибки API
- Тестирование при медленном соединении и без сети
- Документация и передача исходников с примерами использования
Оцениваем проект бесплатно — пишите, рассчитаем стоимость и сроки.
Из нашей практики
Наш клиент — маркетплейс строительных материалов — жаловался на медленный поиск: 1,5–2 секунды ответа при наборе каждого символа. Мы реализовали комбинированный подход: fuse.js по офлайн-кэшу с 3 000 SKU плюс API для нестандартных запросов. Итог: среднее время ответа 60 мс, отказы от поиска снизились на 34%, число завершённых заказов выросло.
В другом проекте — медицинское приложение на iOS и Android — добавили историю поиска и подсветку совпадений. Время до выбора препарата сократилось с 8 до 4 касаний экрана.
Наш опыт — более 50 реализованных мобильных проектов, 6 лет в нативной и кроссплатформенной разработке. Гарантируем соблюдение сроков и качество кода.
Сроки и стоимость
| Объём работ | Срок |
|---|---|
| Базовый поиск: дебаунс + API + подсказки | 2–3 дня |
| Расширенный: история + кеш + подсветка | 3–4 дня |
| Полный пакет: офлайн FTS + RN/Flutter | 4–5 дней |
Стоимость базового пакета — от 35 000 ₽, расширенного — от 55 000 ₽. Оставьте заявку: рассчитаем стоимость вашего проекта индивидуально и оценим сроки за один рабочий день.
Стандартный компонент Apple UISearchController покрывает базовый поиск, но кастомное автодополнение дает контроль над алгоритмом ранжирования, офлайн-режимом и дизайном подсказок.







