Beste RxJava-operators voor REST-applicaties in Android

Er zijn veel verschillende operators in het standaard RxJava-pakket. Sommigen van hen zijn echt robuust en ingewikkeld om te gebruiken, anderen nogal eenvoudig. Maar er is één ding dat veel RxJava-operators gemeen hebben:

De meeste daarvan zul je nooit gebruiken

Als dagelijkse Android-ontwikkelaar die alle dingen in RxJava doet, streefde ik vaak naar de zip () -operator, en telkens als ik dat niet deed. Ik heb altijd iets beters gevonden dan dit, of een situatie die deze operator niet zal behandelen. Ik zeg niet dat zip () helemaal geen gebruik heeft, iemand vindt het misschien leuk en als dat voor jou werkt - Dat is geweldig. Maar laten we enkele operators bespreken die ik super handig vind, en ze zijn geweldig en gemakkelijk te gebruiken in een op REST gebaseerde applicatie.

En hier zijn ze:

  • delen()
  • replay (1) .refCount ()
Als je al weet wat ze doen, kun je net zo goed een klap voor me achterlaten en op dit punt verder lezen.

Warm of koud ?

Een van de belangrijkste dingen die vaak moeilijk te begrijpen is, is of het waarneembare Heet of Koud is. Er zijn veel geweldige artikelen die het uitleggen en ik ben niet van plan het nog een keer te doen, in plaats daarvan zal ik je voorbeelden laten zien van hoe het in de praktijk werkt.

Doet het er tenslotte toe of je wat waarneembaar Heet, Koud of Warm noemt?

Nee.

Het enige dat telt is: als het het werk doet.

Over het algemeen heb je misschien twee soorten observables nodig:

  • waarneembaar dat de laatst uitgestuurde waarde onthoudt en deze naar alle nieuwe abonnees verzendt,
  • waarneembaar dat de laatst uitgestraalde waarde niet meer weet.

Praten is goedkoop. Laat me de code zien.

Laten we zeggen dat we in onze applicatie enkele gegevens willen downloaden en weergeven. Laten we ons de eenvoudigste manier voorstellen om het te doen:

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

Daar. Laten we nu foutafhandeling toevoegen:

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

Super goed. Maar laten we ook een voortgangsgebeurtenis en lege lijst toevoegen voor de beste UX:

val usersObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
usersObservable
         .filter {it.isNotError ()}
         .subscribe {view.update (it)}
usersObservable
         .filter {it.isError ()}
         .subscribe {view.showErrorMessage ()}
usersObservable
         .map (false)
         .startWith (true)
         .subscribe {progressLoading.visibility = it}
usersObservable
         .map (it.isEmpty ())
         .startWith (false)
         .subscribe {emptyMessage.visibility = it}

Nu ... is er iets mis in deze code? We kunnen het testen.

@Test
leuke test () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (Observable.never ())
            .doOnNext {println (it)}

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

}

In de bovenstaande test is erObservable.just () in plaats van een REST-aanvraag. Waarom mergeWith (nooit ())? Omdat we niet willen dat ons waarneembaar is voordat elke abonnee de kans heeft zich erop te abonneren. Een vergelijkbare situatie (eindeloos waarneembaar) kan worden opgemerkt wanneer een verzoek wordt geactiveerd door invoer door de gebruiker. Deze zaak zal later in het artikel worden behandeld. Ook werden de vier observables die in het vorige voorbeeld werden gebruikt, vereenvoudigd om alleen subscribe (). We kunnen het gedeelte van de planner negeren, omdat alles in één thread gebeurt. Het eindresultaat is:

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

Elk abonnement op gebruikersOrError Observable heeft println () geactiveerd, wat betekent dat we in de echte toepassing slechts vier aanvragen hebben geactiveerd in plaats van één. Dit kan een zeer gevaarlijke situatie zijn. Stel je voor dat we in plaats van een potentieel onschadelijk GET-verzoek POST zouden maken of een andere methode zouden aanroepen die de status van de gegevens of toepassing wijzigt. Hetzelfde verzoek wordt vier keer uitgevoerd en er worden bijvoorbeeld vier identieke berichten of opmerkingen gemaakt.

Gelukkig kunnen we dit eenvoudig oplossen door replay (1) .refCount () toe te voegen.

@Test
leuke `test replay refCount-operators` () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (Observable.never ())
            .doOnNext {println (it)}
            .replay (1)
            .refCount ()

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

}

Resultaat van deze test is:

[user1, user2]

Geweldig, we hebben ons abonnement met succes gedeeld met alle abonnees. Nu bestaat er geen gevaar meer onnodige verzoeken in te dienen. Laten we dezelfde waarneembare operator met share () proberen in plaats van replay (1) .refCount ().

@Test
leuke `test share operator` () {
    val usersOrError = Observable.just (listOf ("user1", "user2"))
            .mergeWith (Observable.never ())
            .doOnNext {println (it)}
            .delen()

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

}

Verrassend (of niet) is het resultaat hetzelfde als eerder:

[user1, user2]

Laten we nog twee tests maken om het verschil tussen share () en replay (1) .refCount () te zien. Deze keer bellen we ons nepverzoek nadat we een click-gebeurtenis van de gebruiker hebben ontvangen. Klikgebeurtenis wordt bespot door aPublishSubject. Extra regel: doOnNext {println ("1")} laat zien welke abonnee het evenement van usersOrError heeft ontvangen

De eerste test zal share () gebruiken en de tweede test (1) .refCount.

@Test
leuke `test share operator met click` () {
    val clickEvent = PublishSubject.create  ()

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

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

    clickEvent.onNext (Any ()) // klik uitvoeren

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

}

Resultaat:

1
2

@Test
leuke `test replay refCount-operators met klik` () {
    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 ()) // klik uitvoeren

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

}

Resultaat:

1
2
3
4

Gevolgtrekking

Zowel share () als replay (1) .refCount () zijn belangrijke operators om REST-aanvragen af ​​te handelen, en nog veel meer. Elke keer als u op meerdere plaatsen hetzelfde nodig hebt, is dit de beste manier om te gaan. Denk maar aan als u wilt dat uw waarnemer de nieuwste gebeurtenis onthoudt en doorgeeft aan elke nieuwe abonnee of misschien bent u geïnteresseerd in eenmalige bewerking. Hier zijn enkele voorbeelden van echte toepassingen:

  • getUsers (), getPosts () of vergelijkbaar waarneembaar dat wordt gebruikt om de gegevens op te halen, zal hoogstwaarschijnlijk usereplay (1) .refCount (),
  • updateUser (), addComment () zijn daarentegen eenmalige bewerkingen en in dit geval zal share () het beter doen,
  • passerende click-gebeurtenis verpakt in Observable - RxView.clicks (view) - moet ook de operator share () hebben, om er zeker van te zijn dat de click-gebeurtenis naar elke abonnee wordt uitgezonden.

TL; DR

  • share () -> deelt het waarneembare voor alle abonnees, geeft de laatste waarde niet uit aan nieuwe abonnees
  • replay (1) .refCount () -> deelt het waarneembare voor alle abonnees en geeft de laatste waarde aan elke nieuwe abonnee

Als je mijn werk leuk vindt, klik je op de knop and en laat je me weten wat je denkt in de reacties.