Най-добрите оператори на RxJava за приложения REST в Android

В стандартния пакет RxJava има много различни оператори. Някои от тях са наистина здрави и сложни за използване, други доста прости. Но има едно нещо, което много оператори на RxJava имат общо:

Повечето от тях никога няма да използвате

Като всекидневен разработчик на Android, който прави всички неща в RxJava, много пъти се стремях да използвам zip () оператор и всеки път не успях да го направя. Винаги съм намирал нещо по-добро от него или ситуация, която този оператор няма да покрие. Не казвам, че zip () изобщо няма използване, някой може да го хареса и ако това работи за вас - Това е чудесно. Но нека да обсъдим някои оператори, които намирам за супер полезни и те са страхотни и лесни за използване в REST базирано приложение.

И ето ги:

  • дял()
  • повторение (1) .refCount ()
Ако вече знаете какво правят, може и да оставите хлопка за мен и да приключите да чета в този момент.

Топло или студено?

Едно от най-важните неща, което често е трудно да се разбере, дали наблюдаваното е горещо или студено. Има много страхотни статии, които го обясняват и нямам намерение да го правя отново, вместо това ще ви покажа примери за това как работи на практика.

В края на краищата, има ли значение дали вашето обаждане е известно, горещо, студено или топло?

Не.

Важното е само ако свърши работата.

Като цяло може да са ви необходими два вида наблюдения:

  • видим, който запомня последната излъчена стойност и я излъчва на всички нови абонати,
  • наблюдаема, която не помни последната му излъчена стойност.

Разговорът е евтин. Покажете ми кода.

Нека да кажем, че в нашето приложение искаме да изтеглим някои данни и да ги покажем. Нека си представим най-лесния начин да го направите:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
         . подпишете се {view.update (it)}
         . подпишете се {view.update (it)}

Там. Сега нека добавим обработката на грешки:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
usersObservable
         .filter {it.isNotError ()}
         . подпишете се {view.update (it)}
usersObservable
         .filter {it.isError ()}
         .отпишете се {view.showErrorMessage ()}

Страхотен. Но също така нека добавим събитие за напредък и празен списък за най-доброто UX:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
usersObservable
         .filter {it.isNotError ()}
         . подпишете се {view.update (it)}
usersObservable
         .filter {it.isError ()}
         .отпишете се {view.showErrorMessage ()}
usersObservable
         .map (фалшиво)
         .startWith (истина)
         .subscribe {progressLoading.visibility = it}
usersObservable
         .map (it.isEmpty ())
         .startWith (фалшиво)
         .subscribe {emptyMessage.visibility = it}

Сега ... има ли нещо нередно в този код? Можем да го тестваме.

@Тест
забавен тест () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (Observable.never ())
            .doOnNext {println (it)}

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

}

В горния тест имаOObservable.just () вместо REST заявка. Защо mergeWith (никога ())? Тъй като не искаме нашето наблюдение да завърши, преди всеки абонат да има възможност да се абонира за него. Подобна ситуация (безкрайно наблюдаемо) може да се забележи, когато някаква заявка се задейства чрез въвеждане на кликване от потребителя. Този случай ще бъде разгледан по-късно в статията. Също така четирите наблюдения, използвани в предишния пример, бяха опростени само за абонамент (). Можем да игнорираме част от планиращите, тъй като всичко се случва в една тема. Крайният резултат е:

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

Всеки абонамент за наблюдаеми потребителиOrError е задействал println (), което означава, че в приложението в реалния живот ние просто задействахме четири заявки вместо едно. Това може да бъде много опасна ситуация. Представете си, ако вместо потенциално безобидна GET заявка, бихме направили POST или извикаме някакъв друг метод, който променя състоянието на данните или приложението. Една и съща заявка ще бъде изпълнена четири пъти и например ще бъдат създадени четири еднакви публикации или коментари.

За щастие, лесно можем да го поправим, като добавим преиграване (1) .refCount ().

@Тест
забавно `тестово преиграване на оператори refCount` () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (Observable.never ())
            .doOnNext {println (it)}
            .replay (1)
            .refCount ()

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

}

Резултатът от този тест е:

[user1, user2]

Страхотно, успешно споделихме абонамента си между всички абонати. Сега няма заплаха от отправяне на ненужни множество заявки. Нека опитаме същия наблюдаем с оператор share (), вместо да преиграе (1) .refCount ().

@Тест
забавно `оператор за тестване на акции` () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (Observable.never ())
            .doOnNext {println (it)}
            .дял()

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

}

Изненадващо (или не) резултатът е същият като предишния:

[user1, user2]

За да видим разликата между share () и playy (1) .refCount (), нека направим още два теста. Този път ще се обадим на нашата фалшива заявка след получаване на събитие за кликване от потребителя. Събитието за щракване ще се подиграва от aPublishSubject. Допълнителен ред: doOnNext {println ("1")} ще покаже кой абонат е получил събитието от usersOrError

Първият тест ще използва share (), а втори повторен (1) .refCount.

@Тест
забавен `тест оператор за споделяне с click` () {
    val clickEvent = PublishSubject.create  ()

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

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

    clickEvent.onNext (Any ()) // извърши щракване

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

}

Резултат:

1
2

@Тест
забавно `тестване преиграйте операторите на refCount с click` () {
    val clickEvent = PublishSubject.create  ()

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

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

    clickEvent.onNext (Any ()) // извърши щракване

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

}

Резултат:

1
2
3
4

заключение

И споделеното (), и повторното изпълнение (1) .refCount () са важни оператори за обработка на REST заявки и много други. Всеки път, когато имате нужда от едно и също наблюдение на няколко места, това е най-добрият начин. Помислете само ако искате вашите наблюдателни да запомнят последното събитие и да го предадете на всеки нов абонат или може би се интересувате от еднократна операция. Ето няколко примера за приложения в реалния живот:

  • getUsers (), getPosts () или подобно наблюдаемо, което се използва за получаване на данните, най-вероятно ще използва replay (1) .refCount (),
  • updateUser (), addComment (), от друга страна, са еднократни операции и в този случай share () ще се справи по-добре,
  • събитие за преминаващ клик, увито в наблюдаемо - RxView.clicks (изглед) - също трябва да има оператор share (), за да е сигурно, че събитието за щракване ще бъде излъчвано на всеки абонат.

TL; DR

  • share () -> споделя наблюдаемия за всички абонати, не излъчва най-новата стойност на новите абонати
  • преиграване (1) .refCount () -> споделя наблюдаваното за всички абонати и излъчва най-новата стойност на всеки нов абонат

Ако ви харесва работата ми натиснете бутона ❤ и ме уведомете какво мислите в коментарите.