Кращі оператори RxJava для програм REST в Android

У стандартному пакеті RxJava існує багато різних операторів. Деякі з них справді надійні та складні у використанні, інші досить прості. Але є одне, що має багато спільних для багатьох операторів RxJava:

Більшість з них ви ніколи не будете використовувати

Як щоденний розробник Android, який робить усі речі в RxJava, я багато разів прагнув використовувати zip () оператор, і кожен раз мені це не вдалося. Я завжди знаходив щось краще, ніж це, або ситуацію, яку цей оператор не охопить. Я не кажу, що zip () взагалі не має звичок, комусь це може сподобатися, і якщо це працює для вас - Це чудово. Але давайте обговоримо деякі оператори, які мені здаються надзвичайно корисними, і вони чудові та прості у використанні в додатку на основі REST.

І ось вони:

  • поділитися ()
  • повтор (1) .refCount ()
Якщо ви вже знаєте, що вони роблять, ви можете залишити хлопчик для мене і закінчити читання.

Гаряче чи холодне?

Одне з найважливіших речей, яке часто важко зрозуміти, - це спостережуване «гаряче» чи «холодне». Існує багато чудових статей, що пояснюють це, і я не маю наміру робити це знову, замість цього я покажу вам приклади того, як це працює на практиці.

Зрештою, чи має значення ваш дзвінок, який спостерігається гаряче, холодне чи тепле?

Немає.

Все, що важливо, це: якщо вона виконує роботу.

Загалом вам можуть знадобитися два види спостережень:

  • помітно, що запам'ятовує останнє випромінене значення та передає його всім новим абонентам,
  • спостерігається, що не пам'ятає останнього випромінюваного значення.

Розмова дешева. Покажіть мені код.

Скажімо, у нашій програмі ми хочемо завантажити деякі дані та відобразити їх. Давайте уявимо найпростіший спосіб зробити це:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
         .subscribe {view.update (it)}
         .subscribe {view.update (it)}

Там. Тепер додамо обробку помилок:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
користувачі, що спостерігаються
         .filter {it.isNotError ()}
         .subscribe {view.update (it)}
користувачі, що спостерігаються
         .filter {it.isError ()}
         .subscribe {view.showErrorMessage ()}

Чудово. Але також додамо події прогресу та порожній список для найкращого UX:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
користувачі, що спостерігаються
         .filter {it.isNotError ()}
         .subscribe {view.update (it)}
користувачі, що спостерігаються
         .filter {it.isError ()}
         .subscribe {view.showErrorMessage ()}
користувачі, що спостерігаються
         .map (помилково)
         .startWith (вірно)
         .subscribe {progressLoading.visibility = it}
користувачі, що спостерігаються
         .map (it.isEmpty ())
         .startWith (помилково)
         .subscribe {emptyMessage.visibility = it}

Тепер… чи щось не так у цьому коді? Ми можемо протестувати.

@Test
веселий тест () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (спостерігається. коли ())
            .doOnNext {println (це)}

    usersOrError.subscribe ()
    usersOrError.subscribe ()
    usersOrError.subscribe ()
    usersOrError.subscribe ()

}

У наведеному вище тесті замість REST-запиту єObservable.just (). Чому mergeWith (never ())? Оскільки ми не хочемо, щоб наше спостереження було завершено до того, як кожен абонент має шанс підписатися на нього. Аналогічну ситуацію (безкінечне спостереження) можна помітити, коли якийсь запит викликається введенням натискання користувача. Ця справа буде висвітлена пізніше у статті. Також чотири спостережувані дані, використані в попередньому прикладі, були спрощені лише для підписки (). Ми можемо ігнорувати частину планувальників, оскільки все відбувається в один потік. Кінцевий результат:

[user1, user2]
[user1, user2]
[user1, user2]
[user1, user2]

Кожна підписка на спостережуваний користувачOrError викликала println (), що означає, що в реальному застосуванні ми просто запустили чотири запити замість одного. Це може бути дуже небезпечною ситуацією. Уявіть, якби замість потенційно нешкідливого запиту GET, ми зробили б POST або зателефонували до іншого способу, який змінює стан даних чи програми. Один і той же запит буде виконуватися чотири рази, і, наприклад, будуть створені чотири однакові повідомлення або коментарі.

На щастя, ми можемо це легко виправити, додавши повтор (1) .refCount ().

@Test
весело `тестова повторна операція refCount` () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (спостерігається. коли ())
            .doOnNext {println (це)}
            .відтворення (1)
            .refCount ()

    usersOrError.subscribe ()
    usersOrError.subscribe ()
    usersOrError.subscribe ()
    usersOrError.subscribe ()

}

Результатом цього тесту є:

[user1, user2]

Чудово, що ми успішно поділилися між усіма підписниками. Тепер немає жодної загрози зробити непотрібні кілька запитів. Дозвольте спробувати те саме, що спостерігається з оператором share (), а не повторити (1) .refCount ().

@Test
весело `тестовий оператор акцій` () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (спостерігається. коли ())
            .doOnNext {println (це)}
            .share ()

    usersOrError.subscribe ()
    usersOrError.subscribe ()
    usersOrError.subscribe ()
    usersOrError.subscribe ()

}

Дивно (або ні) результат такий же, як і попередній:

[user1, user2]

Щоб засвідчити різницю між share () та повтором (1) .refCount (), зробимо ще два тести. Цього разу ми зателефонуємо на наш підроблений запит після отримання від користувача події кліку. APublishSubject знущається з події натискання. Додатковий рядок: doOnNext {println ("1")} покаже, який абонент отримав подію від користувачівOrError

Перший тест буде використовувати share (), а другий повтор (1) .refCount.

@Test
весело `тестовий оператор з натисканням` () {
    val clickEvent = PublishSubject.create  ()

    val usersOrError = clickEvent
            .flatMap {Observable.just (listOf ("user1", "user2"))}
            .share ()

    usersOrError.doOnNext {println ("1")} .subscribe ()
    usersOrError.doOnNext {println ("2")} .subscribe ()

    clickEvent.onNext (Будь-який ()) // виконати клацання

    usersOrError.doOnNext {println ("3")} .subscribe ()
    usersOrError.doOnNext {println ("4")} .subscribe ()

}

Результат:

1
2

@Test
весело `тестуйте повторну операцію refCount з click` () {
    val clickEvent = PublishSubject.create  ()

    val usersOrError = clickEvent
            .flatMap {Observable.just (listOf ("user1", "user2"))}
            .відтворення (1)
            .refCount ()

    usersOrError.doOnNext {println ("1")} .subscribe ()
    usersOrError.doOnNext {println ("2")} .subscribe ()

    clickEvent.onNext (Будь-який ()) // виконати клацання

    usersOrError.doOnNext {println ("3")} .subscribe ()
    usersOrError.doOnNext {println ("4")} .subscribe ()

}

Результат:

1
2
3
4

Висновок

І share (), і повтор (1) .refCount () є важливими операторами для обробки REST-запитів та багато іншого. Кожен раз, коли вам потрібно однакове спостереження у кількох місцях, це найкращий шлях. Подумайте, якщо ви хочете, щоб ваш спостерігач запам'ятав останню подію та передав її кожному новому абоненту або, можливо, вас цікавить одноразова операція. Ось кілька прикладів застосування в реальному житті:

  • getUsers (), getPosts () або подібні спостережувані, які використовуються для отримання даних, швидше за все, використовуватимуть відтворення (1) .refCount (),
  • updateUser (), addComment (), з іншого боку, - це разові операції, і в цьому випадку share () зробить краще,
  • подія, що проходить, натиснута у "Спостережуване" - RxView.clicks (view) - також повинна мати оператора share (), щоб бути впевненим, що подія натискання буде транслюватися кожному підписчику.

TL; DR

  • share () -> ділиться спостережуваним для всіх передплатників, не передає останнє значення новим абонентам
  • refy (1) .refCount () -> ділиться спостережуваним для всіх передплатників і видає останнє значення кожному новому абоненту

Якщо вам подобається моя робота, натисніть кнопку ❤ і дайте мені знати, що ви думаєте в коментарях.