Pull to refresh

Реализация поиска с использованием RxJava

Reading time 4 min
Views 20K
Original author: Amit Shekhar
В данной статье будет рассмотрена оптимальная и компактная реализация поиска с использованием RxJava для Android, отсеивающая ненужные результаты и уменьшающая количество бесполезных сетевых вызовов.

Пример поиска
Оригинал написан 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 передает дальше по цепочке только те элементы, которые продержались без вызова новых событий в течение указанного времени ожидания.
debounce example image

Filter


Данный оператор используется для отсеивания нежелательных строк, например, пустой строки, чтобы избежать ненужных сетевых вызовов.

DistinctUntilChanged


Данный оператор используется для того, чтобы избежать дублирования сетевых вызовов. Например, последний поисковой запрос был «abc», затем пользователь удалил «c» и заново ввел «c». Результат снова «abc». Если сетевой вызов уже в процессе с тем же параметром, то оператор distinctUntilChanged не даст осуществить аналогичный вызов повторно. Таким образом, оператор distinctUntilChanged отсеивает повторяющиеся последовательно переданные ему элементы.
distinctUntilChanged example image

SwitchMap


В данном примере этот оператор используется для исключения сетевых вызовов, результаты которых больше не нужно показывать пользователю. Например, последним поисковым запросом был «ab» и есть работающий сетевой вызов для этого запроса, но пользователь в это время вводит «abc». Результат для «ab» больше не нужен, нужен только результат для «abc». SwitchMap спешит на помощь. Он передает дальше результаты только последнего запроса, игнорируя остальные.

Javadoc описывает switchMap следующим образом:

Возвращает новый Observable, применяя переданную функцию для каждого полученного элемента Observable, но передавая далее элементы, созданные только последним полученным Observable.

Мы сделали это! Только представьте насколько было бы сложно реализовать поиск без RxJava.

Если вы хотите рассмотреть полный пример, то взгляните на следующий проект.
Tags:
Hubs:
+6
Comments 13
Comments Comments 13

Articles