Οι καλύτεροι χειριστές RxJava για εφαρμογές REST στο Android

Υπάρχουν πολλοί διαφορετικοί χειριστές στο πρότυπο πακέτο RxJava. Ορισμένα από αυτά είναι πραγματικά εύρωστα και πολύπλοκα στη χρήση, άλλα αρκετά απλά. Αλλά υπάρχει ένα πράγμα που πολλοί φορείς εκμετάλλευσης RxJava έχουν από κοινού:

Οι περισσότεροι από αυτούς δεν θα χρησιμοποιήσετε ποτέ

Ως καθημερινός προγραμματιστής του Android που κάνει όλα τα πράγματα στο RxJava, πολλές φορές προσπάθησα να χρησιμοποιήσω τον χειριστή zip (), και κάθε φορά που απέτυχα να το κάνω. Έχω πάντα βρει κάτι καλύτερο από αυτό, ή μια κατάσταση που ο φορέας αυτός δεν θα καλύψει. Δεν λέω ότι το zip () δεν έχει καθόλου χρήσεις, κάποιος μπορεί να το αρέσει και αν αυτό λειτουργεί για σας - Αυτό είναι υπέροχο. Αλλά ας συζητήσουμε κάποιους χειριστές που θεωρώ εξαιρετικά χρήσιμοι και είναι εξαιρετικά και εύχρηστοι σε εφαρμογή βασισμένη στο REST.

Και εδώ είναι:

  • μερίδιο()
  • αναπαραγωγή (1) .refCount ()
Εάν γνωρίζετε ήδη τι κάνουν, ίσως να αφήσετε ένα χτύπημα για μένα και να τελειώσετε την ανάγνωση σε αυτό το σημείο.

Ζεστό ή κρύο ?

Ένα από τα πιο σημαντικά πράγματα που συχνά είναι δύσκολο να καταλάβουμε είναι εάν το παρατηρούμενο είναι Ζεστό ή Ψυχρό. Έχουν υπάρξει πολλά μεγάλα άρθρα που το εξηγούν και δεν έχω καμία πρόθεση να το κάνω ξανά, αλλά θα σας δείξω παραδείγματα για το πώς λειτουργεί στην πράξη.

Εξάλλου, έχει σημασία αν έχετε καλέσει κάποιο παρατηρήσιμο Hot, Cold ή Warm;

Οχι.

Το μόνο που έχει σημασία είναι: αν κάνει τη δουλειά.

Γενικά μπορεί να χρειαστείτε δύο είδη παρατηρήσεων:

  • παρατηρήσιμο που θυμάται την τελευταία εκπεμπόμενη αξία και το εκπέμπει σε όλους τους νέους συνδρομητές,
  • παρατηρήσιμο που δεν θυμάται την τελευταία εκπεμπόμενη αξία του.

Η συζήτηση είναι φτηνή. Δείξε μου τον κωδικό.

Ας πούμε ότι στην εφαρμογή μας θέλουμε να κατεβάσουμε κάποια δεδομένα και να τα δείξουμε. Ας φανταστούμε τον ευκολότερο τρόπο να το κάνετε:

val χρήστεςObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
         .subscribe {view.update (it)}
         .subscribe {view.update (it)}

Εκεί. Τώρα ας προσθέσουμε το χειρισμό σφαλμάτων:

val χρήστεςObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
usersObservable
         .filter {it.isNotError ()}
         .subscribe {view.update (it)}
usersObservable
         .filter {it.isError ()}
         .subscribe {view.showErrorMessage ()}

Μεγάλος. Αλλά επίσης να προσθέσουμε ένα συμβάν προόδου και μια κενή λίστα για το καλύτερο UX:

val χρήστεςObservable = service.getUsers ()
         .subscribeOn (networkScheduler)
         .observeOn (UiScheduler)
usersObservable
         .filter {it.isNotError ()}
         .subscribe {view.update (it)}
usersObservable
         .filter {it.isError ()}
         .subscribe {view.showErrorMessage ()}
usersObservable
         .map (ψευδές)
         .startWith (true)
         .subscribe {progressLoading.visibility = it}
usersObservable
         .map (it.isEmpty ())
         .startWith (false)
         .subscribe {emptyMessage.visibility = it}

Τώρα ... υπάρχει κάτι λάθος σε αυτόν τον κώδικα; Μπορούμε να το δοκιμάσουμε.

@Δοκιμή
δοκιμή διασκέδασης () {
    val usersOrError = Παρατηρήσεις.just (listOf ("user1", "user2"))
            .mergeWith (Obsble.never ())
            .doOnNext {println (it)}

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

}}

Στην παραπάνω δοκιμή, υπάρχειObservable.just () αντί για ένα αίτημα REST. Γιατί να συγχωνεύσετε (ποτέ ()); Επειδή δεν θέλουμε να ολοκληρωθεί η παρακολούθησή μας πριν κάθε συνδρομητής έχει την ευκαιρία να εγγραφεί σε αυτόν. Παρόμοια κατάσταση (ατελείωτη παρατηρούμενη) μπορεί να παρατηρηθεί όταν κάποιο αίτημα ενεργοποιείται από την είσοδο κλικ χρήστη. Αυτή η περίπτωση θα καλυφθεί αργότερα στο άρθρο. Επίσης, τα τέσσερα παρατηρήσιμα στοιχεία που χρησιμοποιήθηκαν στο προηγούμενο παράδειγμα απλοποιήθηκαν μόνο για την εγγραφή (). Μπορούμε να αγνοήσουμε μέρος των προγραμματιστών, αφού όλα συμβαίνουν σε ένα νήμα. Το τελικό αποτέλεσμα είναι:

[χρήστη1, χρήστη2]
[χρήστη1, χρήστη2]
[χρήστη1, χρήστη2]
[χρήστη1, χρήστη2]

Κάθε συνδρομή προς παρατηρητές usersOrError έχει ενεργοποιήσει println () που σημαίνει ότι στην πραγματική εφαρμογή απλά ενεργοποιήσαμε τέσσερα αιτήματα αντί ενός. Αυτό μπορεί να είναι μια πολύ επικίνδυνη κατάσταση. Φανταστείτε αν αντί για δυνητικά αβλαβή GET αίτημα, θα κάναμε POST ή να καλέσουμε κάποια άλλη μέθοδο που αλλάζει την κατάσταση των δεδομένων ή της εφαρμογής. Το ίδιο αίτημα θα εκτελεστεί τέσσερις φορές και για παράδειγμα τέσσερις ίδιες θέσεις ή σχόλια θα δημιουργηθούν.

Ευτυχώς, μπορούμε να το διορθώσουμε εύκολα προσθέτοντας την επανάληψη (1) .refCount ().

@Δοκιμή
διασκέδαση `δοκιμή επαναλήψεων refCount operators '() {
    val usersOrError = Παρατηρήσεις.just (listOf ("user1", "user2"))
            .mergeWith (Obsble.never ())
            .doOnNext {println (it)}
            .replay (1)
            .refCount ()

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

}}

Αποτέλεσμα αυτής της δοκιμής είναι:

[χρήστη1, χρήστη2]

Μεγάλη, μοιραστήκαμε με επιτυχία τη συνδρομή μας μεταξύ όλων των συνδρομητών. Τώρα δεν υπάρχει απειλή να κάνετε περιττές πολλαπλές αιτήσεις. Ας δοκιμάσουμε τον ίδιο παρατηρήσιμο με τον τελεστή share () αντί για την επανάληψη (1) .refCount ().

@Δοκιμή
fun `φορέας τεστ δοκιμής '() {
    val usersOrError = Παρατηρήσεις.just (listOf ("user1", "user2"))
            .mergeWith (Obsble.never ())
            .doOnNext {println (it)}
            .μερίδιο()

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

}}

Παραδόξως (ή όχι) το αποτέλεσμα είναι το ίδιο με το προηγούμενο:

[χρήστη1, χρήστη2]

Για να δείτε τη διαφορά μεταξύ share () και replay (1) .refCount (), ας κάνουμε δύο ακόμα δοκιμές. Αυτή τη φορά θα καλέσουμε το ψεύτικο αίτημά μας αφού λάβουμε το συμβάν κλικ από το χρήστη. Το συμβάν κλικ θα χλευαστεί από το aPublishSubject. Πρόσθετη γραμμή: doOnNext {println ("1")} θα δείξει ποιος συνδρομητής έλαβε το συμβάν από usersOrError

Η πρώτη δοκιμή θα χρησιμοποιεί το share () και το δεύτερο ένα replay (1) .refCount.

@Δοκιμή
διασκέδαση `δοκιμή μετοχή τελεστή με κλικ '() {
    κύριο clickEvent = PublishSubject.create  ()

    val χρήστεςOrError = clickEvent
            .flatMap {Παρατηρήσεις.just (listOf ("user1", "user2"))}
            .μερίδιο()

    χρήστεςOrError.doOnNext {println ("1")} .subscribe ()
    χρήστεςOrError.doOnNext {println ("2")} .subscribe ()

    clickEvent.onNext (Any ()) // κάντε κλικ

    usersOrError.doOnNext {println ("3")} .subscribe ()
    χρήστεςOrError.doOnNext {println ("4")} .subscribe ()

}}

Αποτέλεσμα:

1
2

@Δοκιμή
fun `δοκιμή επαναλήψεων refCount φορείς με κλικ '() {
    κύριο clickEvent = PublishSubject.create  ()

    val χρήστεςOrError = clickEvent
            .flatMap {Παρατηρήσεις.just (listOf ("user1", "user2"))}
            .replay (1)
            .refCount ()

    χρήστεςOrError.doOnNext {println ("1")} .subscribe ()
    χρήστεςOrError.doOnNext {println ("2")} .subscribe ()

    clickEvent.onNext (Any ()) // κάντε κλικ

    usersOrError.doOnNext {println ("3")} .subscribe ()
    χρήστεςOrError.doOnNext {println ("4")} .subscribe ()

}}

Αποτέλεσμα:

1
2
3
4

συμπέρασμα

Τόσο το share () όσο και το relay (1) .refCount () είναι σημαντικοί χειριστές για να χειρίζονται τις αιτήσεις REST και πολλά άλλα. Κάθε φορά που χρειάζεστε το ίδιο παρατηρήσιμο σε πολλαπλά σημεία, είναι ο καλύτερος τρόπος να πάτε. Απλά σκεφτείτε αν θέλετε ο παρατηρητής σας να θυμάται το τελευταίο γεγονός και να το μεταβιβάσει σε κάθε νέο συνδρομητή ή ίσως να σας ενδιαφέρει μόνο μια φορά λειτουργία. Ακολουθούν ορισμένα παραδείγματα εφαρμογών πραγματικής ζωής:

  • Οι εντολές getUsers (), getPosts () ή παρόμοια παρατηρήσιμα στοιχεία που χρησιμοποιούνται για τη λήψη των δεδομένων θα χρησιμοποιούν, κατά πάσα πιθανότητα, usereplay (1) .refCount (),
  • updateUser (), addComment () από την άλλη πλευρά είναι μόνο μία φορά λειτουργίες και στην περίπτωση αυτή το share () θα κάνει καλύτερα,
  • παθητικό συμβάν κλικ που είναι τυλιγμένο στο Observation - RxView.clicks (προβολή) - θα πρέπει επίσης να διαθέτει τον τελεστή share (), για να βεβαιωθείτε ότι το γεγονός κλικ θα μεταδοθεί σε κάθε συνδρομητή.

TL · DR

  • share () -> μοιράζεται το παρατηρήσιμο σε όλους τους συνδρομητές, δεν εκπέμπει την τελευταία τιμή σε νέους συνδρομητές
  • (1) .refCount () -> μοιράζεται το παρατηρήσιμο σε όλους τους συνδρομητές και εκπέμπει την τελευταία τιμή σε κάθε νέο συνδρομητή

Αν σας αρέσει το έργο μου χτυπήστε ❤ κουμπί και με ενημερώστε τι σκέφτεστε στα σχόλια.