Использование наблюдаемого шаблона в Android

Когда я изучал потоки и реактивное программирование, я узнал о RxJava, но отложил его изучение, потому что это казалось сложным.

В конце концов я решил попробовать, создав приложение, в котором я легко нашел шаблон Observable, и поэтому я решил создать клон стандартного приложения часов Android.

Приложение

Я назвал проект «ClockClone». Моя цель состояла в том, чтобы определить все варианты использования стандартного приложения часов, где шаблон наблюдателя был применим, а затем воссоздать его самостоятельно с помощью RxJava, чтобы я мог изучить его.

Чтобы все было просто и понятно, я не стал делать приложение полным клоном — не весь функционал был воспроизведен. Среди прочего, на самом деле не работают будильники и нет полноэкранных уведомлений.

Вариант использования

Основные функции приложения основаны на четырех вкладках на главном экране приложения «Часы»:

  • Будильники — установка будильников и управление ими. Будет только постоянное хранение данных о тревогах, и никакие тревоги фактически не будут запланированы.
  • Мировые часы — просмотр времени и погодных условий в крупных городах мира.
  • Секундомер — запустить секундомер. Он также может показывать промежуточное время и время прохождения круга.
  • Таймер — запуск простого таймера обратного отсчета.

Я буду обсуждать только те части приложения, которые относятся к RxJava.

Код

Весь код можно найти на Github здесь. Код в этой статье сокращен для краткости.

RxJava

RxJava — это Java-реализация ReactiveX API. Чтобы процитировать их домашнюю страницу:

ReactiveX — это сочетание лучших идей
шаблона Observer, шаблона Iterator и функционального программирования.

Все операции в ReactiveX вращаются вокруг использования Observables. Они функционируют аналогично Java Futures, но созданы для работы с последовательностями асинхронных значений, а не с отдельными.

Наблюдаемые объекты генерируют три основных типа событий: onNext, onComplete и onError. Последовательность Observable состоит из нуля или более событий onNext и завершается либо событием onComplete, либо событием onError, но не обоими одновременно.

ReactiveX поставляется с большим набором операторов, которые можно использовать для самых разных действий с Observables, таких как преобразования, фильтрация, комбинирование Observables, агрегатные операции и т. д. Я использовал только некоторые из них в приложении, о которых я расскажу ниже. .

Для приложения я использую RxJava 3, в частности версию 3.0.0. Полный список версий можно найти на странице релизов на Github.

Предположения

Вы знакомы со следующим:

  • Шаблон наблюдателя
  • Модернизация
  • Комната
  • Архитектура приложения для Android
  • Платформа параллелизма Java
  • Кинжал

Используемые библиотеки

Секундомер

Наблюдаемые данные, необходимые для секундомера, включают количество времени, прошедшее в миллисекундах с момента запуска — время выполнения, количество времени, прошедшее в миллисекундах с тех пор, как пользователь в последний раз щелкнул «круг» — время круга, состояние (OFF, RUNNING, PAUSED ) — в основном для пользовательского интерфейса и уведомлений, а также для списка времени разделения и круга, который будет обновляться каждый раз, когда нажимается «разделение» или «круг». Придумав это, я обнаружил, что для ручной генерации событий необходим особый вид Observable.

Предметы

Субъекты выступают как наблюдатели данных и как Observables одновременно. Предметы бывают четырех видов:

  1. PublishSubject: отправляет значения в Observer с точки подписки. Все значения, отправленные до подписки, никогда не будут получены.
  2. ReplaySubject: воспроизводит поток с самого начала, а также выдает новые значения.
  3. BehaviorSubject: то же, что и PublishSubject, но выдает самое последнее значение до того, как была сделана подписка. Хороший пример — поток с начальным состоянием.
  4. AsyncSubject: отправляет в Observer только последнее значение. Это значение генерируется только тогда, когда субъект вызывает onComplete() .

Я выбрал BehaviorSubject, чтобы пользовательский интерфейс мог определить последнее состояние Observable при его перезапуске из фона или при изменении конфигурации.

Чтобы отслеживать время и отправлять обновления через фиксированные промежутки времени, я использовал ScheduledThreadPoolExecutor. Это лучший вариант, чем обработчик, поскольку обработчики выполняются в основном (UI) потоке.

Таймер

Таймер работает очень похоже на секундомер. Наблюдаемые данные включают оставшееся время в миллисекундах и состояние (OFF, COUNTING или PAUSED). Я также использовал BehaviorSubject и ScheduledThreadPoolExecutor.

Мировое время

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

Наблюдаемые данные включают таймер для обновления отображаемого текущего времени, погодных условий и списка городов. Отмечу, что у меня были трудности с работой над World Clock — я продолжал менять структуру кода, пока не нашел структуру классов, которая работала хорошо.

Центральным элементом данных для этой функции является идентификатор часового пояса. Это выглядит так:

Africa/Lagos

Его можно использовать для получения текущего времени в крупных городах, а также погодных условий. Я использовал данные из TZDB для создания списка часовых поясов и сохранил его в ресурсах приложения в виде CSV-файла.

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

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

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

Погодные условия

Я решил использовать AccuWeather, чтобы иметь возможность работать с Retrofit и опробовать адаптер вызовов RxJava.

Создание клиентского интерфейса с адаптером выполняется вызовом addCallAdapterFactory() объекта Builder.

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

Наблюдаемые за одним событием

Стандартный Observable создан для работы с последовательностями асинхронных задач, но сетевой запрос — это всего лишь одна задача. Хотя можно использовать Observable с Retrofit, в RxJava есть специальные типы, созданные для обработки разовых событий.

  • Одиночный: излучает либо onSuccess , либо onError. onSuccess выдается с одним элементом.
  • Completable: выдает либо onCompleted , либо onError, но ни один элемент не выдается с onCompleted.
  • Возможно: любое из событий onSuccess, onComplete или onError будет сгенерировано. onSuccess есть один элемент.

Отмена подписки

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

Сетевые запросы являются потенциальным источником утечек памяти, что, в свою очередь, может привести к сбою. В ReactiveX Subscribers — это специальные типы, представляющие подписку на Observable, но в RxJava они известны как Disposables.

Disposables будут использоваться для отмены запросов при необходимости. У них есть метод dispose(), который отменяет подписку на Observables.

Observable возвращает объект Disposable через вызов subscribe(). CompositeDisposables помогают избавиться от набора Disposables одновременно, аналогично операции forEach для стандартной коллекции.

Обновлять каждую минуту

Наблюдаемый интервал генерирует событие с бесполезным элементом каждую минуту, чтобы сигнализировать пользовательскому интерфейсу об обновлении текущего времени. Чтобы обновлять список каждую минуту, я использовал оператор combineLatest().

будильники

Я решил использовать базу данных для отслеживания аварийных сигналов с целью их просмотра, изменения и удаления, и поэтому использовал Room с адаптером RxJava.

Из этой статьи я узнал, что Observable, Maybe, Single и Completable можно использовать в качестве возвращаемых типов для метода Room DAO. Кроме того, можно использовать Flowable .

Flowable — это Observable с поддержкой противодавления, когда Observable отправляет события быстрее, чем Observer может их обработать. Observable можно преобразовать в Flowable, используя toFlowable() с BackpressureStrategy в качестве параметра. Типы одиночных событий, такие как Может быть и Один раз, не используют стратегию в качестве параметра конверсии. Я использую только BackpressureStrategy.BUFFER в своем коде. Перейдите по ссылкам, чтобы узнать больше о Противодавлении и Растекающихся материалах.

Преобразование в LiveData

Все типы Observable могут быть преобразованы в LiveData, чтобы их можно было использовать с ViewModels. Их нужно преобразовать в Flowable, с помощью которого они передаются методу LiveDataReactiveStreams.fromPublisher().

Одна вещь, которую я не реализовал в LiveData, — это обработка ошибок. Событие ошибки от Observable приведет к тому, что LiveData вызовет исключение RuntimeException, что приведет к сбою приложения.

Ожидается, что вы преобразуете свои возможные ошибки в значимые объекты состояния пользовательского интерфейса, прежде чем передавать их в LiveData. Посмотрите эту реализацию из примера Google.

Заключение

У меня есть некоторые мысли о RxJava. Это мощная библиотека с большим потенциалом, и мне кажется, что я едва касался поверхности для ClockClone. Тем не менее, я научился использовать его с пользой для шаблона Observable на Android.

Мне также нравится тот факт, что он не является эксклюзивным для разработки Android, поэтому он может пригодиться в других областях. Я рад, что наконец научился этому. Надеюсь, вы тоже чему-то научились.