Beste RxJava-operatører for REST-applikasjoner i Android

Det er mange forskjellige operatører i standard RxJava-pakke. Noen av dem er veldig robuste og kompliserte å bruke, andre ganske enkle. Men det er en ting som mange RxJava-operatører har til felles:

De fleste av dem vil du aldri bruke

Som hverdags Android-utvikler som gjør alle tingene i RxJava, prøvde jeg mange ganger å bruke zip () -operatør, og hver gang jeg ikke klarte å gjøre det. Jeg har alltid funnet noe bedre enn det, eller en situasjon som denne operatøren ikke vil dekke. Jeg sier ikke at zip () ikke har noen bruksområder i det hele tatt, noen kan like det, og hvis det fungerer for deg - Det er flott. Men la oss diskutere noen operatører som jeg synes er veldig nyttige, og de er flotte og enkle å bruke i REST-basert applikasjon.

Og her er de:

  • dele()
  • avspilling (1) .refCount ()
Hvis du allerede vet hva de gjør, kan du like godt la et klapp for meg og være ferdig med å lese på dette tidspunktet.

Varm eller kald ?

Noe av det viktigste som ofte er vanskelig å forstå, er om den observerbare er varm eller kald. Det har vært mange gode artikler som forklarer det, og jeg har ingen intensjoner om å gjøre det igjen, i stedet vil jeg vise eksempler på hvordan det fungerer i praksis.

Tross alt, betyr det noe om du kaller noen observerbare varme, kalde eller varme?

Nei.

Alt som betyr noe er: hvis den gjør jobben.

Generelt kan det hende du trenger to slags observerbare ting:

  • observerbar som husker sist utsendt verdi, og sender den ut til alle nye abonnenter,
  • observerbar som ikke husker den sist utsendte verdien.

Snakk er billig. Vis meg koden.

La oss si at i applikasjonen vår ønsker vi å laste ned noen data og vise dem. La oss forestille oss den enkleste måten å gjøre det på:

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

Der. La oss nå legge til feilhåndtering:

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

Flott. Men la oss også legge til en fremdriftshendelse og en tom liste for den 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 (sann)
         .subscribe {progressLoading.visibility = it}
usersObservable
         .map (it.isEmpty ())
         .startWith (false)
         .subscribe {emptyMessage.visibility = it}

Nå ... er det noe galt i denne koden? Vi kan teste det.

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

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

}

I testen ovenfor er detObservable.just () i stedet for en REST-forespørsel. Hvorfor flette sammen (aldri ())? Fordi vi ikke ønsker at vårt observerbare skal fullføres før hver abonnent har en sjanse til å abonnere på det. En lignende situasjon (observerbar endelig slutt) kan legges merke til når en eller annen forespørsel utløses av brukerklikkinnspill. Denne saken vil bli behandlet senere i artikkelen. Også de fire observerbare tingene som ble brukt i forrige eksempel ble forenklet til kun å abonnere (). Vi kan ignorere planleggernes del, siden alt skjer i en tråd. Det endelige resultatet er:

[bruker1, bruker2]
[bruker1, bruker2]
[bruker1, bruker2]
[bruker1, bruker2]

Hvert abonnement på brukereOrError observerbart har utløst println () noe som betyr at vi i den virkelige applikasjonen bare utløste fire forespørsler i stedet for en. Dette kan være en veldig farlig situasjon. Se for deg at i stedet for en potensielt ufarlig GET-forespørsel, ville vi lage POST eller kalt en annen metode som endrer status for data eller applikasjon. Den samme forespørselen vil bli utført fire ganger, og for eksempel opprettes fire identiske innlegg eller kommentarer.

Heldigvis kan vi enkelt fikse det ved å legge til replay (1) .refCount ().

@Test
morsomme `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 ()

}

Resultatet av denne testen er:

[bruker1, bruker2]

Flott, vi delte vårt abonnement mellom alle abonnenter. Nå er det ingen trussel om å komme med unødvendige flere forespørsler. La oss prøve det samme observerbare med share () -operatøren i stedet for å spille på nytt (1) .refCount ().

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

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

}

Overraskende (eller ikke) er resultatet det samme som tidligere:

[bruker1, bruker2]

For å se forskjell mellom deling () og replay (1) .refCount () la oss gjøre to tester til. Denne gangen vil vi ringe vår falske forespørsel etter å ha fått klikkhendelse fra brukeren. Klikkhendelse blir spottet av aPublishSubject. Ekstra linje: doOnNext {println ("1")} vil vise hvilken abonnent som fikk hendelsen fra usersOrError

Den første testen bruker share () og den andre replayen (1) .refCount.

@Test
morsom `test dele operatør med klikk` () {
    val clickEvent = PublishSubject.create  ()

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

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

    clickEvent.onNext (Any ()) // utfør klikk

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

}

Resultat:

1
2

@Test
morsomme `test replay refCount-operatører med klikk` () {
    val clickEvent = PublishSubject.create  ()

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

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

    clickEvent.onNext (Any ()) // utfør klikk

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

}

Resultat:

1
2
3
4

Konklusjon

Både share () og replay (1) .refCount () er viktige operatører for å håndtere REST-forespørsler, og mye mer. Hver gang du trenger det samme observerbare flere steder, er det den beste veien å gå. Bare tenk hvis du vil at din observerbare skal huske den siste hendelsen og gi den til hver nye abonnent, eller kanskje du er interessert i engangsoperasjon. Her er noen eksempler på applikasjoner i det virkelige liv:

  • getUsers (), getPosts () eller lignende observerbare som brukes for å få dataene vil trolig usereplay (1) .refCount (),
  • updateUser (), addComment () er derimot bare engangsoperasjoner og i dette tilfellet vil dele () gjøre det bedre,
  • passerende klikkhendelse innpakket i Observable - RxView.clicks (view) - bør også ha share () -operatør, for å være sikker på at klikkhendelsen sendes til hver abonnent.

TL; DR

  • share () -> deler det observerbare til alle abonnenter, gir ikke den siste verdien til nye abonnenter
  • replay (1) .refCount () -> deler det observerbare til alle abonnenter og sender ut nyeste verdi til hver nye abonnent

Hvis du liker arbeidet mitt, trykker du på ❤-knappen og gir meg beskjed om hva du synes i kommentarer.