← Назад к вопросам

Как реализовать отправку поискового запроса только после остановки ввода пользователя?

2.0 Middle🔥 151 комментариев
#Сетевое взаимодействие

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация отложенного поиска при остановке ввода

Для реализации отправки поискового запроса после остановки ввода пользователя используется паттерн Debouncing (устранение дребезга). Этот подход предотвращает отправку множества запросов при быстром наборе текста и выполняется только один запрос после паузы в вводе.

Основные подходы реализации

1. Использование Handler и Runnable (базовый способ)

class SearchActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())
    private val debounceDelay = 500L // Задержка в миллисекундах
    
    private val searchRunnable = Runnable {
        performSearch(searchEditText.text.toString())
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_search)
        
        val searchEditText = findViewById<EditText>(R.id.searchEditText)
        
        searchEditText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                // Удаляем предыдущий отложенный вызов
                handler.removeCallbacks(searchRunnable)
                
                // Устанавливаем новый отложенный вызов
                if (!s.isNullOrEmpty()) {
                    handler.postDelayed(searchRunnable, debounceDelay)
                }
            }
            
            override fun afterTextChanged(s: Editable?) {}
        })
    }
    
    private fun performSearch(query: String) {
        // Реализация поискового запроса
        viewModel.search(query)
    }
    
    override fun onDestroy() {
        handler.removeCallbacks(searchRunnable)
        super.onDestroy()
    }
}

2. Использование Coroutines и Flow (современный способ)

class SearchViewModel : ViewModel() {
    private val searchQuery = MutableStateFlow("")
    private val debounceDuration = 500L
    
    init {
        observeSearchQueries()
    }
    
    private fun observeSearchQueries() {
        viewModelScope.launch {
            searchQuery
                .debounce(debounceDuration) // Ключевой оператор для дебаунса
                .filter { query -> 
                    query.length >= 2 // Минимальная длина для поиска
                }
                .distinctUntilChanged() // Игнорируем повторяющиеся значения
                .collect { query ->
                    performSearch(query)
                }
        }
    }
    
    fun updateSearchQuery(query: String) {
        searchQuery.value = query
    }
    
    private fun performSearch(query: String) {
        // Выполнение поискового запроса
    }
}

// В Activity/Fragment
searchEditText.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        viewModel.updateSearchQuery(s.toString())
    }
    
    override fun afterTextChanged(s: Editable?) {}
})

3. Использование RxJava (реактивный подход)

class RxSearchActivity : AppCompatActivity() {
    private val compositeDisposable = CompositeDisposable()
    private val debounceTimeout = 500L
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val searchEditText = findViewById<EditText>(R.id.searchEditText)
        
        val disposable = RxTextView.textChanges(searchEditText)
            .skipInitialValue()
            .debounce(debounceTimeout, TimeUnit.MILLISECONDS)
            .filter { text -> text.length >= 2 }
            .distinctUntilChanged()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { text ->
                performSearch(text.toString())
            }
        
        compositeDisposable.add(disposable)
    }
    
    override fun onDestroy() {
        compositeDisposable.clear()
        super.onDestroy()
    }
}

Ключевые аспекты реализации

Оптимальная задержка:

  • 300-500 мс для текстового поиска
  • 800-1000 мс для сложных запросов или медленных сетей
  • Можно добавить прогресс-бар для индикации ожидания

Дополнительные улучшения:

// Автоматическая отмена предыдущих запросов
private var currentSearchJob: Job? = null

fun performSearch(query: String) {
    currentSearchJob?.cancel() // Отменяем предыдущий запрос
    
    currentSearchJob = viewModelScope.launch {
        try {
            showLoading()
            val results = repository.search(query)
            updateResults(results)
        } catch (e: CancellationException) {
            // Запрос был отменен - игнорируем
        } catch (e: Exception) {
            showError(e)
        } finally {
            hideLoading()
        }
    }
}

Рекомендации по реализации:

  1. Минимальная длина запроса - не выполняйте поиск при 1-2 символах
  2. Отмена предыдущих запросов - критично для сетевых операций
  3. Кэширование результатов - улучшает UX при повторных запросах
  4. Обработка поворотов экрана - используйте ViewModel для сохранения состояния
  5. Индикация загрузки - показывайте прогресс во время ожидания ответа

Архитектурные considerations

Для чистой архитектуры рекомендуется выносить логику debouncing в доменный слой или use case. В MVVM паттерне оптимально использовать StateFlow/SharedFlow с оператором debounce. Для производительности учитывайте:

  • Использование Dispatchers.IO для сетевых запросов
  • Paging 3 Library для постраничной загрузки результатов
  • Кэширование в памяти с помощью cachedIn оператора Flow

Итог: Наиболее современный и рекомендуемый подход - использование Kotlin Coroutines Flow с оператором debounce, что обеспечивает чистоту кода, корректную работу с жизненным циклом и простую тестируемость.

Как реализовать отправку поискового запроса только после остановки ввода пользователя? | PrepBro