$30 off During Our Annual Pro Sale. View Details »

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. RxJava día a día
    @yaircarreno

    View Slide

  2. 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.

    View Slide

  3. Actores de Reactive Programming
    Los protagonistas del mundo Reactive Programming son: Observable,
    Observer, Subscriber 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, 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.

    View Slide

  4. Anatomía de rx.Observable
    rx.Observable 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 tweets

    Observable temperature

    Observable customers

    Observable response

    Observable completionCallback

    View Slide

  5. Anatomía de rx.Observable
    El contrato de un Observable 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 Observable just(T x) {
    return Observable.create(subscriber -> {
    subscriber.onNext(x);
    subscriber.onCompleted();
    });
    }

    View Slide

  6. 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.

    View Slide

  7. Consejos
    Una serie de recomendaciones a tener en
    cuenta en reactive programming.

    View Slide

  8. Consejos
    1. No usar una variable global dentro de un operador:
    Observable 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();
    });

    View Slide

  9. 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 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.

    View Slide

  10. 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 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.

    View Slide

  11. 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 obs = simple();
    obs.subscribeOn(Scheduler A);
    obs.subscribe();
    //En este caso las operaciones corren sobre el scheduler A. Esta es la forma correcta!
    Observable obs = simple();
    obs.subscribeOn(Scheduler A)
    .subscribe();

    View Slide

  12. 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.

    View Slide

  13. Consejos
    8. Utiliza los RxMarbles si necesitas aclarar el comportamiento de cualquier
    operador.

    View Slide

  14. Schedulers
    Tópicos sobre la concurrencia en reactive
    programming.

    View Slide

  15. Schedulers

    Todas las operaciones corren sobre el scheduler default (main thread).

    No existe thread pools.

    No existe emisión asincrónica de eventos.

    View Slide

  16. Schedulers

    Todas las operaciones corren sobre el scheduler A.

    No existe thread pools.

    No existe emisión asincrónica de eventos.

    View Slide

  17. 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.

    View Slide

  18. Schedulers

    Se generan diferentes hilos de ejecución para la operación bloqueante.

    Existe thread pools.

    Existe emisión asincrónica de eventos.

    View Slide

  19. 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.

    View Slide

  20. 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).

    View Slide

  21. 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();

    View Slide

  22. 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();

    View Slide

  23. Escenarios
    Escenarios comunes en el día a día del
    desarrollo con reactive programming.

    View Slide

  24. 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.

    View Slide

  25. 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 dataEmitter;
    AsyncValueListener(AsyncEmitter 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());
    }
    }

    View Slide

  26. Escenarios en Rx
    Dominio del Observable creado a partir de la API:
    //Observable que envuelve la reacción de la API
    pubic Observable dataChangedObs() {
    return Observable.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.

    View Slide

  27. 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.

    View Slide

  28. 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 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
    );

    View Slide

  29. 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:

    View Slide

  30. Escenarios en Rx
    Dominio de Observables generados a partir de la API:
    Observable rxLookupFlight(String flightNo) {
    return Observable.defer(() ->
    Observable.just(lookupFlight(flightNo)));
    }
    Observable 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 = rxLookupFlight("LOT 783");
    Observable passenger = rxFindPassenger(42);
    Observable ticket = flight
    .zipWith(passenger, ( f , p ) -> bookTicket( f , p ));
    .subscribe(this::sendEmail);
    Dominio de la subscripción:

    View Slide

  31. 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.

    View Slide

  32. Escenarios en Rx
    Dominio de Observables involucrados:
    //Api Reactive como por ejemplo Retrofit
    public interface myAPIService {
    @Get("api/token")
    Observable getToken();
    @Get("api/product")
    Observable getProduct(String token);
    @Get("api/detailproduct")
    Observable getDetailProduct(String id);
    }
    //SharePreferences Manager
    public interface myPreferencesManager {
    Observable getTokenSaved();
    Observable saveToken(String token);
    }
    //Inspección de validez del token
    private Observable validateToken(String token) {
    if (token != null && !expirado) {
    return Observable.just(token);
    } else {
    return myAPIService.getToken().doOnNext(token -> myPreferencesManager::saveToken);
    }
    }

    View Slide

  33. 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.

    View Slide

  34. 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.

    View Slide

  35. Gracias

    View Slide