В данной статье будет рассмотрена оптимальная и компактная реализация поиска с использованием RxJava для Android, отсеивающая ненужные результаты и уменьшающая количество бесполезных сетевых вызовов.
Оригинал написан 16 октября 2017. Перевод вольный.
В настоящее время большинство приложений, которые мы ежедневно используем, предоставляют возможность быстрого поиска. Поэтому реализация поиска — важная задача. Нам же, как разработчикам, важно реализовать поиск лучшим путем.
Рассмотрим как реализовать поиск лучшим способом с использованием RxJava. Не забывайте, в RxJava есть операторы для всего.
Лично я верю, что можно решить любую проблему очень легко с использованием RxJava, что может оказаться очень сложным без RxJava. RxJava прекрасная технология.
Взгляните на элементы RxJava, которые мы будем использовать для реализации поиска:
Сначала нужно сделать SearchView observable. Сделаем его с использованием PublishSubject. Я использую SearchView из Android. View может быть любым, с функционалом как у EditText. Для реализации observable нужно реализовать listener для изменения текста в поле.
Теперь обсудим почему используются именно эти операторы и как они работают вместе.
Этому оператору передаются параметры времени. В данном случае он отлавливает события ввода пользователем текста, например, «a», «ab», «abc». Ввод часто происходит очень быстро и это чревато большим количеством сетевых вызовов. Но пользователь обычно заинтересован только в результатах для «abc». Итак, нужно отбросить выполнение запросов для «a», «ab». Оператор debounce спешит на помощь. Он ожидает бездействия пользователя в течение переданного в параметре времени. Если во время ожидания будет осуществлен какой-либо ввод, то счётчик ожидания сбросится, отсчет начнется заново, предыдущий переданный результат, например, «a» будет отброшен. Таким образом, оператор debounce передает дальше по цепочке только те элементы, которые продержались без вызова новых событий в течение указанного времени ожидания.
Данный оператор используется для отсеивания нежелательных строк, например, пустой строки, чтобы избежать ненужных сетевых вызовов.
Данный оператор используется для того, чтобы избежать дублирования сетевых вызовов. Например, последний поисковой запрос был «abc», затем пользователь удалил «c» и заново ввел «c». Результат снова «abc». Если сетевой вызов уже в процессе с тем же параметром, то оператор distinctUntilChanged не даст осуществить аналогичный вызов повторно. Таким образом, оператор distinctUntilChanged отсеивает повторяющиеся последовательно переданные ему элементы.
В данном примере этот оператор используется для исключения сетевых вызовов, результаты которых больше не нужно показывать пользователю. Например, последним поисковым запросом был «ab» и есть работающий сетевой вызов для этого запроса, но пользователь в это время вводит «abc». Результат для «ab» больше не нужен, нужен только результат для «abc». SwitchMap спешит на помощь. Он передает дальше результаты только последнего запроса, игнорируя остальные.
Javadoc описывает
Возвращает новый
Мы сделали это! Только представьте насколько было бы сложно реализовать поиск без RxJava.
Если вы хотите рассмотреть полный пример, то взгляните на следующий проект.
Оригинал написан 16 октября 2017. Перевод вольный.
В настоящее время большинство приложений, которые мы ежедневно используем, предоставляют возможность быстрого поиска. Поэтому реализация поиска — важная задача. Нам же, как разработчикам, важно реализовать поиск лучшим путем.
Рассмотрим как реализовать поиск лучшим способом с использованием RxJava. Не забывайте, в RxJava есть операторы для всего.
Лично я верю, что можно решить любую проблему очень легко с использованием RxJava, что может оказаться очень сложным без RxJava. RxJava прекрасная технология.
Взгляните на элементы RxJava, которые мы будем использовать для реализации поиска:
- Publish Subject: Если вы не сталкивались с этим оператором, то взгляните на эту статью
- Filter
- Debounce
- DistinctUntilChanged
- SwitchMap
Начнем
Сначала нужно сделать SearchView observable. Сделаем его с использованием PublishSubject. Я использую SearchView из Android. View может быть любым, с функционалом как у EditText. Для реализации observable нужно реализовать listener для изменения текста в поле.
public class RxSearchObservable {
public static Observable<String> fromView(SearchView searchView) {
final PublishSubject<String> subject = PublishSubject.create();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
subject.onComplete();
return true;
}
@Override
public boolean onQueryTextChange(String text) {
subject.onNext(text);
return true;
}
});
return subject;
}
Замечание от ConstOrVar:
Observable для SearchView будет не совсем корректно работать. Вы на него подписываетесь в onCreate(), но при срабатывании onQueryTextSubmit() произойдет отписка, так как вызовется onComplete. Получается, что повторный поиск не будет работать. Чтобы повторный поиск работал, нужно избавиться от subject.onComplete();
Замечание от Scrobot и BFS:Далее необходимо вызвать созданный метод и добавить вызовы операторов, как в примере ниже.
Не стоит использовать Subject, он существует только для 1 цели: соединять императивный стиль с реактивным. Лучше использовать Observable.create(). Поиск это ровно тот кейс, когда нужно думать о Backpressure, а поскольку практически все сегодня используют RxJava2, то там эта проблема решена с помощью Flowable, и лучше этот кейс зарефакторить на него.
RxSearchObservable.fromView(searchView)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(new Predicate<String>() {
@Override
public boolean test(String text) throws Exception {
if (text.isEmpty()) {
return false;
} else {
return true;
}
}
})
.distinctUntilChanged()
.switchMap(new Function<String, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(String query) throws Exception {
return dataFromNetwork(query);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String result) throws Exception {
textViewResult.setText(result);
}
});
Теперь обсудим почему используются именно эти операторы и как они работают вместе.
Debounce
Этому оператору передаются параметры времени. В данном случае он отлавливает события ввода пользователем текста, например, «a», «ab», «abc». Ввод часто происходит очень быстро и это чревато большим количеством сетевых вызовов. Но пользователь обычно заинтересован только в результатах для «abc». Итак, нужно отбросить выполнение запросов для «a», «ab». Оператор debounce спешит на помощь. Он ожидает бездействия пользователя в течение переданного в параметре времени. Если во время ожидания будет осуществлен какой-либо ввод, то счётчик ожидания сбросится, отсчет начнется заново, предыдущий переданный результат, например, «a» будет отброшен. Таким образом, оператор debounce передает дальше по цепочке только те элементы, которые продержались без вызова новых событий в течение указанного времени ожидания.
Filter
Данный оператор используется для отсеивания нежелательных строк, например, пустой строки, чтобы избежать ненужных сетевых вызовов.
DistinctUntilChanged
Данный оператор используется для того, чтобы избежать дублирования сетевых вызовов. Например, последний поисковой запрос был «abc», затем пользователь удалил «c» и заново ввел «c». Результат снова «abc». Если сетевой вызов уже в процессе с тем же параметром, то оператор distinctUntilChanged не даст осуществить аналогичный вызов повторно. Таким образом, оператор distinctUntilChanged отсеивает повторяющиеся последовательно переданные ему элементы.
SwitchMap
В данном примере этот оператор используется для исключения сетевых вызовов, результаты которых больше не нужно показывать пользователю. Например, последним поисковым запросом был «ab» и есть работающий сетевой вызов для этого запроса, но пользователь в это время вводит «abc». Результат для «ab» больше не нужен, нужен только результат для «abc». SwitchMap спешит на помощь. Он передает дальше результаты только последнего запроса, игнорируя остальные.
Javadoc описывает
switchMap
следующим образом:Возвращает новый
Observable
, применяя переданную функцию для каждого полученного элемента Observable
, но передавая далее элементы, созданные только последним полученным Observable
.Мы сделали это! Только представьте насколько было бы сложно реализовать поиск без RxJava.
Если вы хотите рассмотреть полный пример, то взгляните на следующий проект.