Blog


RxJava. Использование оператора share

Сегодня расскажу про удобный реактивный подход в обработке ввода текста в EditText. В качестве примера возьмем поисковую строку в SearchView.

Задача

Поиск обычно занимает какое-то время. Неважно, ходим мы за данными на сервер, или читаем локальную базу. Пользователь может вводить символы в поисковую строку гораздо быстрее, чем будут отрабатывать такие запросы. Поэтому нам нужно установить какое-то пороговое значение временной задержки, чаще которой не вызывать поиск. Помимо этого, нам может потребоваться отображать в UI изменения в режиме реального времени, без задержек.

Решение

Без использования Rx в Android этого можно добиться с помощью обычного Runnable и Handler, что не очень удобно и громоздко. Благодаря библиотеке RxBinding мы можем применить известный оператор debounce и получить изящное решение в виде:

RxSearchView.queryTextChanges(searchView) 
 .debounce(500, TimeUnit.MILLISECONDS) // задержка в 500 мс 
 .subscribe(query -> mPresenter.searchRequest(query));

Теперь, независимо от скорости ввода текста, запросы на сервер будут уходить не чаще двух раз в секунду.
Однако, что делать, если нам потребуется делать обновления в UI при каждом вводе нового символа? Например, менять подсказку. При текущей реализации эти обновления UI будут происходить с той же задержкой в 500 мс, что совсем не user-friendly.

На наше счастье, в Rx есть возможность широковещательного уведомления нескольких подписчиков. Для этого нужно вызвать оператор .share() на нашем Observable и вуаля!

Observable<String> sharedTextChanges = RxSearchView.queryTextChages(searchViw).share()
 
sharedTextChanges 
 .debounce(500, TimeUnit.MILLISECONDS) // use debounce 
 .subscribe(query -> mPresenter.searchRequest(query)); 
 
sharedTextChanges 
 .subscribe(query -> mPresenter.updateUI(query));

Мы два раза подписались на Observable: с debounce и без него. Теперь UI обновляется в режиме реального времени, при каждом вводе символа, а запросы на сервер уходят не чаще раза в 500 мс.

Что под капотом?

Оператор .share() — это обертка на цепью операторов .publish().refcount(). Они позволяет “расшарить” испускаемые потоком объекты. Рассмотрим эти операторы подробнее.

Оператор .publish( ) — превращает Observable в ConnectableObservable.

rxjava publish operator scheme

“ConnectedObservable” — это такой Observable, который не выпускает данные, пока на нем не вызовут оператор .connect(). Таким образом, мы можем дождаться, пока все Subscriber-ы не подпишутся на Observable, и только затем испускать данные.

Оператор .refcount() берет на себя функции по управлению несколькими подписчиками. Согласно документации,

Returns an Observable that stays connected to this ConnectableObservable as long as there is at least one subscription to this ConnectableObservable.

RxJava operator refcount

refcount() — держит в памяти количество подписчиков на результирующий Observable и не отключается от источника ConnectedObservable пока все не отпишутся.

 

Вот и все. Очень эффективно и элегантно, как всегда с rx. Надеюсь, этот пост оказался полезным для вас!




Настройка proxy в Android Studio

На днях мне пришлось разворачивать Android Studio IDE на машине с корпоративным прокси. Тема не нова, и уже обсуждалась на stackoverflow и в блогах. Однако, эта задача не решается сходу — приходиться покопаться. Поэтому я решил создать пошаговую инструкцию по настройке IDE Android-разработчика в условиях прокси.

Все действия мы будем проводить на windows-машине. Для linux алгоритм будет аналогичный.

Итак, при первом запуске Android Studio предлагает настроить прокси

Жмем Setup Proxy, вводим адрес прокси-сервера и свои учетные данные:

Адрес прокси можно узнать с помощью команды (windows)

ipconfig /all | find /i "Dns Suffix"

Протестируйте соединение с помощью кнопки Check connection на этом же окне. Если все ок, идем дальше.

 

Все ок, идем дальше. В появившемся после запуска IDE окне прокси нужно снова прописать параметры proxy для http и https:


Эти же настройки можно прописать в файле gradle.properties:

systemProp.http.proxyPassword=<PASSWORD>
systemProp.http.proxyHost=<PROXY_URL>
systemProp.http.proxyUser=<LOGIN>
systemProp.http.proxyPort=<PORT>
systemProp.https.proxyPassword=<PASSWORD>
systemProp.https.proxyHost=<PROXY_URL>
systemProp.https.proxyUser=<LOGIN>
systemProp.https.proxyPort=<PORT>

Однако, имейте ввиду, что настройки прокси IDE перезаписывают настройки проекта.

Если сейчас попытаться собрать проект, то, скорее всего, сборка закончится неуспешно с ошибкой

SSLHandshakeException: sun.security.validator.ValidatorException: PKIX fix

Gradle пытается достучаться до серверов репозиториев, не имея сертификатов. Нам необходимо добавить их в хранилища. Для этого сначала добавляем в gradle.properties следующие строчки:

systemProp.javax.net.ssl.trustStore=<ANDROID STUDIO PATH>\\jre\\lib\\security\\cacerts
systemProp.javax.net.ssl.trustStorePassword=changeit

Здесь мы указываем путь и пароль к хранилищу сертификатов. Пароль по умолчанию — changeit. Если вы не меняли его, он остался таким же.

Как же добавить сертификаты в хранилище?

Установка сертификатов

При запуске проекте IDE предлагает принять сертификаты. Их следует принять, однако это не поможет автоматически. Нам необходимо импортировать сертификаты в хранилище сертификатов cacerts IDE и JVM. Для этого необходимо выполнить следующие шаги:

  1. Скачать сертификат. Сделать это можно с помощью браузера или openssl
  2. Импортировать сертификат в в хранилища с помощью keytool

Чтобы импортировать загруженный на шаге 1 сертификат, на Windows-машине необходимо запустить командную строку от администратора и выполнить:

keytool -import -alias <alias> -keystore C:\Progra~1\Android\Android Studio3.0\jre\jre\lib\security\cacerts -file <path_to/certificate_file>

Также, необходимо добавить этот сертификат в другие хранилища cacerts (JVM и Android Studio):

keytool -import -alias <alias> -keystore <path_to_studio>\.AndroidStudio3.0\system\tasks\cacerts -file <path_to/certificate_file>
 keytool -import -alias <alias> -keystore "C:\Progra~1\Java\jre_V.V.V\lib\security\cacerts" <path_to/certificate_file>

альтернативно, вместо того, чтобы добавлять, можно копировать сертификаты между хранилищами с помощью команды:

keytool -importkeystore -srckeystore <path_to_studio>\.AndroidStudio3.0\system\tasks\cacerts -destkeystore C:\Progra~1\Java\jre_V.V.V\lib\security\cacerts -v
 password changeit

После импорта сертификатов почистите кэш gradle в папке C:\Users\<User>\.gradle и перезагрузите систему. Если при попытке обратиться к хранилищу cacerts IDE выдает ошибку Access denied, запустите Android Studio от администратора.

Запускаем сборку… Проект успешно собирается!

В случае, если импорт сертификатов не помогает, можно заменить адрес загрузки репозиториев с секьюрного https на обычный http:

jcenter {
 url "http://jcenter.bintray.com/"
 }

Git

Помимо gradle, проблемы могут возникнуть и с системой контроля версий. В случае с git необходимо необходимо добавить в глобальные настройки git параметры proxy. Для этого выполнить команду:

 git config --global http.proxy http[s]://userName:password@proxyaddress:port

Если при попытке при попытке pull/push из/в GitLab возникает ошибка

SSL certificate problem: self signed certificate in certificate chain

то следует выполнить следующую команду от администратора:

 git config --system http.sslCAPath <path_to_studio>/.AndroidStudio<v.No>/system/tasks/cacerts

Для возможности push/pull через IDE Android Studio необходимо в настройках  Settings->Version Control->Git в пункте SSH executable указать Native

На этом все, можно работать. Надеюсь, статья оказалась полезной для вас. Буду рад вашим мнениям и комментариям!