Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxJava día a día

RxJava día a día

Un resumen de conceptos de RxJava a tener en cuenta durante el proceso de desarrollo. Consejos y estrategias de implementación que pueden ser de utilidad.

Yair Carreno

January 03, 2017
Tweet

More Decks by Yair Carreno

Other Decks in Technology

Transcript

  1. Cuándo usar Reactive Programming Procesando eventos: clicks, movimientos del mouse,

    ingresos en el teclado, movimientos touch. Interactuando con fuentes datos: API Rest Client, S3 Amazon, Firebase, Legacies, Cloud Storage. Realizando lotes de operaciones: salvar data en caché local, registrar en sistemas de logs, acceder a local disks, integrar SDK's de terceros, operaciones sobre Realm database. Cuando sea necesario manipular y controlar un stream o flujo de información debes pensar en Reactive Programming, en otras palabras casi siempre.
  2. Actores de Reactive Programming Los protagonistas del mundo Reactive Programming

    son: Observable<T>, Observer<T>, Subscriber<T> y los operadores (map, filter, flatMap, merge, zip, combineLatest y muchos más). Observable es el componente central de Rx, son agnósticos en lo referente a ser síncronos o asíncronos. Los observables son por defecto lazy, eso significa que no actúa hasta que sea subscrito. Esto permite que sean reutilizables. A partir de RxJava 2 se ha agregado un nuevo e importante componente. El Flowable<T>, el cuál es similar al Observable pero con la capacidad de soportar backpressure (son las situaciones en las que un Observable emiten items con mayor velocidad que la capacidad del operador o subscriber de consumirlos). Recomendación: Subscribe el observable justo en el momento en que sea necesario usarlo, no antes. En palabras coloquiales, enciende el motor sólo cuando vayas a conducir.
  3. Anatomía de rx.Observable rx.Observable<T> representa un flujo de secuencia de

    datos o información y en el mundo real tenemos muchos ejemplos: Eventos de la interface de usuario, bytes transferidos sobre la red, nuevos pedidos en una tienda online, un tweet, una publicación de una promoción en facebook, la respuesta de una petición GET, etc, etc, etc. Cientos y cientos de eventos que modelan a un Observable. • Observable<Tweet> tweets • Observable<Double> temperature • Observable<Customer> customers • Observable<HttpResponse> response • Observable<Void> completionCallback
  4. Anatomía de rx.Observable El contrato de un Observable<T> es producir

    3 tipos de eventos: OnNext, onCompleted y onError. Se debe garantizar nunca emitir concurrentemente estos eventos, de lo contrario se rompería el contrato. La regla permitida es: OnNext* (OnCompleted | OnError)? El operador just() creando un observable: static <T> Observable<T> just(T x) { return Observable.create(subscriber -> { subscriber.onNext(x); subscriber.onCompleted(); }); }
  5. El poder de Reactive Programming Si bien el CORAZÓN de

    Rx es el rx.Observable, el PODER de Rx es la capacidad de combinar los Operadores. Conocer cada uno de los operadores y aprender a combinarlos es lo que te hará Master Rx. Las principales familias de operadores son (citados sólo algunos): • De creación: just, from, range. • De transformación: map, flatMap, concatMap, flatMapIterable, concat. • De combinación: merge, zip, combineLatest, withLastestFrom, amb. • De muestreo: delay, scan, reduce, collect, debounce. • De filtros: distinct, distinctUntilChanged, skip, first, last, takeFirst, takeUntil, takeWhile, elementAt, all, exits, contain, all. A partir de RxJava2 se han agregado decenas de operadores, haciendo cada vez más baja la necesidad de customizar operadores.
  6. Consejos 1. No usar una variable global dentro de un

    operador: Observable<Long> progress = transferFile(); LongAdder total = new LongAdder(); progress.subscribe(total::add); //Don't do this! 2. No crear múltiples hilos cuando se crea un observable a través de create(), mejor aún evite por todos los medios crear observables usando create(): //Don't do this! Observable.create(s -> { //Thread A new Thread(() -> { s.onNext("one"); s.onNext("two"); }).start(); // Thread B new Thread(() -> { s.onNext("three"); s.onNext("four"); }).start(); });
  7. Consejos 3. Ser cuidadoso al usar operadores de igualdad como

    por ejemplo distinct() o distinctUntilChanged , ya que en el comparar (por ejemplo objetos) si no se implementa correctamente equals() y hashCode() se podría inducir en loops infinitos: //Don't do this! Observable<MyObject> uniqueRandomMyObject = randomMyObject .distinct() .take(10); 4. Nunca llamar en stream infinitos la operación takeLast(), last() ya que estas operaciones pueden quedar en loop infinito e inducir fugas de memoria o degradar el performance de la aplicación.
  8. Consejos 5. El observable deber ser responsable únicamente de la

    lógica de produción, nunca de la concurrencia. La concurrencia es labor de los Schedulers, así que no intentes implementar concurrencia en la lógica de un observable: //Don't do this! Observable<String> obs = Observable.create(subscriber -> { log("Subscribed"); Runnable code = () -> { subscriber.onNext("A"); subscriber.onNext("B"); subscriber.onCompleted(); }; new Thread (code, "Async").start(); }); Importante conocer como trabajan los schedulers en rx. El siguiente bloque es dedicado a los ellos.
  9. Consejos 6. Tener siempre en mente que el observable es

    lazy e immutable, por ejemplo las siguientes definiciones de observables son diferentes: //En este caso las operaciones corren sobre el “main thread” o “default thread”, el scheduler no tiene efecto. Observable<String> obs = simple(); obs.subscribeOn(Scheduler A); obs.subscribe(); //En este caso las operaciones corren sobre el scheduler A. Esta es la forma correcta! Observable<String> obs = simple(); obs.subscribeOn(Scheduler A) .subscribe();
  10. Consejos 7. Existen operadores que se comportan como un elemento

    transversal, es decir que su efecto no interfiere con el flujo principal. Son muy útiles para registrar logs, analítica, salvar en caché, testing, para debug y solución de crashes difíciles de detectar. Son conocidos como las operaciones de doOn...() y algunas son: • doOnCompleted() • doOnEach() • doOnError() • doOnNext() • doOnRequest() • doOnSubscribe() • doOnTerminate() • DoOnUnsubscribe() Se recomienda tenerlas presente y usarlas.
  11. Schedulers • Todas las operaciones corren sobre el scheduler default

    (main thread). • No existe thread pools. • No existe emisión asincrónica de eventos.
  12. Schedulers • Todas las operaciones corren sobre el scheduler A.

    • No existe thread pools. • No existe emisión asincrónica de eventos.
  13. Schedulers • Todas las operaciones corren sobre el scheduler A.

    El scheduler más cercano al observable tiene prioridad. El scheduler B es innecesario y en cambio genera overhead. • No existe thread pools. • No existe emisión asincrónica de eventos.
  14. Schedulers • Se generan diferentes hilos de ejecución para la

    operación bloqueante. • Existe thread pools. • Existe emisión asincrónica de eventos.
  15. subscribeOn / observeOn Rara vez habrá necesidad de controlar la

    concurrencia a través de subscribeOn, sin embargo en las aplicaciones Android es un aspecto clave y no es suficiente sólo con el subscribeOn, es necesario combinar esta operación con observeOn. En Android la concurrencia Rx es compuesta por el par de operaciones subscribeOn y observeOn.
  16. subscribeOn / observerOn En Android si la operación involucra la

    UI los más posible es que C scheduler sea AndroidSchedulers.mainThread. Este scheduler es proporcionado por RxAndroid (io.reactivex:rxandroid). No importa sobre que scheduler procesas las operaciones, al final entregas los resultados al hilo principal (main thread).
  17. subscribeOn / observeOn No importa la posición de subscribeOn, éste

    puede ser definido en cualquier lugar entre el observable y subscribe. //Las siguientes 3 definiciones son iguales: obsX.subscribeOn(A) .flatmap(x -> obsY.blockingOperation().subscribeOn(B)) .filter() .observerOn(C) .subscribe(); obsX.flatmap(x -> obsY.blockingOperation().subscribeOn(B)) .filter() .subscribeOn(A) .observerOn(C) .subscribe(); obsX.flatmap(x -> obsY.blockingOperation().subscribeOn(B)) .filter() .observerOn(C) .subscribeOn(A) .subscribe();
  18. subscribeOn / observeOn Si importa la posición de observeOn, todo

    operador definido encima de observeOn correrá sobre el scheduler definido por subscribeOn (o sobre el default scheduler en caso de no existir algún subscribeOn definido). //En este caso map, filter corren sobre default scheduler y doOnNext corre sobre A scheduler. obs .map() .filter() .observeOn(A) .doOnNext() .subscribe(); //En este caso map corre sobre A scheduler, filter corre sobre B scheduler y doOnNext corre sobre C scheduler. Obs .map() .observeOn(B) .filter() .observeOn(C) .doOnNext() .subscribeOn(A) .subscribe();
  19. Escenarios en Rx 1. Tengo una API que expone listeners,

    callbacks, broadcast receivers ó intentServices y quiero convertirlos a Observables para gestionar todo en un contexto reactive. Imaginemos escenarios en donde queremos: • Reaccionar a los callbacks proporcionados por el SDK Facebook involucrados en el proceso login. • Reaccionar a los listeners de la API de Firebase para intregar con la Realtime Database, Autenticación, Notificaciones y otros features. • Reaccionar a una notificación local enviada a través de un services Android.
  20. Escenarios en Rx Dominio de la API no-reactive: //Listener de

    Firebase que notifica cambios en la data class AsyncValueListener implements ValueEventListener { private final AsyncEmitter<DataSnapshot> dataEmitter; AsyncValueListener(AsyncEmitter<DataSnapshot> dataEmitter) { this.dataEmitter = dataEmitter; } @Override public void onDataChange(DataSnapshot dataSnapshot) { dataEmitter.onNext(dataSnapshot); dataEmitter.onCompleted(); } @Override public void onCancelled(DatabaseError databaseError) { dataEmitter.onError(databaseError.toException()); } }
  21. Escenarios en Rx Dominio del Observable creado a partir de

    la API: //Observable que envuelve la reacción de la API pubic Observable<DataSnapshot> dataChangedObs() { return Observable<DataSnapshot>.fromEmitter( emitter -> reference.addListenerForSingleValueEvent(new AsyncValueListener(emitter)), AsyncEmitter.BackpressureMode.BUFFER); } Dominio de la subscripción: dataChangedObs .map(... some transformation ...) .filter(... some filter ...) .subscribe( dataChanged -> log("Data changed", dataChanged), Throwable::printStackTrace ); Nota: Como se presenta en el bloque anterior, cuando las operaciones involucran la UI en RxAndroid es clave definir subscribeOn/observeOn, el code snippet de acá en adelante no lo incluye.
  22. Escenarios en Rx 2. Tengo un proceso costoso que no

    es reactive y quiero convertirlo en Observable para llevarlo a un contexto reactive. Son esos casos en donde necesito llamar una operación sincrónica bloqueante, es decir una tarea que es necesario completar para continuar con el flujo, por ejemplo: • Guardar una propiedad en SharedPreferences. • Descargar un archivo de la red. • Guardar un audio en SD Card. • Parsear un XML a JSON. • Cargar un documento JSON guardado en los assets. • Enviar un email.
  23. Escenarios en Rx Dominio del Observable creado a partir de

    la operación: private Data load(int id) { //This operation is going to take some time. } //Observable que envuelve la operación Observable<Data> rxLoad (int id) { return Observable.fromCallable(() -> load(id)); } Dominio de la subscripción: rxLoad(1) .map(... some transformation ...) .filter(... some filter ...) .subscribe( data -> log("My data", data), Throwable::printStackTrace );
  24. Escenarios en Rx 3. Migrar operaciones de una API externa

    ya existente que es no-reactive. Supongamos que necesitamos hacer la reserva de un vuelo y posteriormente notificar al usuario vía mail y que para ello necesitamos utilizar los servicios expuestos por una API externa que es no-reactive: Flight lookupFlight(String flightNo) { //... } Passenger findPassenger(long id) { //... } Ticket bookTicket (Flight flight,Passenger passenger ) { //... } SmtpResponse sendEmail(Ticket ticket) { //... } Dominio de la API no-reactive:
  25. Escenarios en Rx Dominio de Observables generados a partir de

    la API: Observable<Flight> rxLookupFlight(String flightNo) { return Observable.defer(() -> Observable.just(lookupFlight(flightNo))); } Observable<Passenger> rxFindPassenger(long id) { return Observable.defer(() -> Observable.just(findPassenger(id))); } A través del operador defer() envolvemos la operación no-reactive en un observable lazy. Observable<Flight> flight = rxLookupFlight("LOT 783"); Observable<Passenger> passenger = rxFindPassenger(42); Observable<Ticket> ticket = flight .zipWith(passenger, ( f , p ) -> bookTicket( f , p )); .subscribe(this::sendEmail); Dominio de la subscripción:
  26. Escenarios en Rx 4. Orquestar un conjunto de operaciones involucradas

    en un flujo. Para mostrar este escenario imaginemos que vamos a consumir una API que utiliza un token con tiempo de expiración, en este caso las operaciones involucradas en el flow podrían ser: • Verificar si tengo un token válido almacenado localmente en las preferencias. • Si tengo un token válido lo utilizo, si no tengo token válido solicito uno nuevo. • Actualizo el nuevo token en las preferencias. • Consumo la API con el token válido. • Consulto la información básica de un producto. • Consulto el detalle de un producto.
  27. Escenarios en Rx Dominio de Observables involucrados: //Api Reactive como

    por ejemplo Retrofit public interface myAPIService { @Get("api/token") Observable<Token> getToken(); @Get("api/product") Observable<Product> getProduct(String token); @Get("api/detailproduct") Observable<DetailProduct> getDetailProduct(String id); } //SharePreferences Manager public interface myPreferencesManager { Observable<Token> getTokenSaved(); Observable<Void> saveToken(String token); } //Inspección de validez del token private Observable<Token> validateToken(String token) { if (token != null && !expirado) { return Observable.just(token); } else { return myAPIService.getToken().doOnNext(token -> myPreferencesManager::saveToken); } }
  28. Escenarios en Rx Dominio de la subscripción: private void loadDetailProduct()

    { myPreferencesManager.getTokenSaved() .flatMap(token -> validateToken::token) .flatMap(token -> myAPIService::getProduct) .flatMap(product - > myAPIService.getDetailProduct(product.id)) .subscribe( detailProduct -> log("My detail product " + detailProduct), Throwable::printStackTrace ); } • El operador flatMap es el orquestador. • Los servicios getTokenSaved, saveToken de Manager Preferences podrían ser creados usando por ejemplo, el escenario 2. • Para salvar en caché local estamos usando una operación doOn...(), es decir que salvar en caché no es requesito para continuar con el flujo. Si quisieramos que fuese obligatorio finalizar primero el salvar en caché para continuar con el flujo sería necesario cambiar el operador doOnNext por la técnica descrita en el escenario 2.
  29. Referencias Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications by

    Tomasz Nurkiewicz, Ben Christensen. ReactiveX/RxJava Reactive Extensions for the JVM – a library for composing asynchronous and event- based programs using observable sequences for the Java VM.