Programación reactiva ▣ Orientada a flujos de datos. Casos típicos: □ Entrada del teclado □ Red □ Base de datos ▣ Desacopla la fuente de los datos de los receptores. □ Evita el uso de listeners que acoplan el código.
‘’ Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new "features". Doug McIlroy The Bell System Technical Journal 1978
Programación funcional reactiva ▣ Introducida en 1997 ▣ Combina los dos modelos anteriores ▣ Nos permite manejar cadenas de datos o “eventos”, aplicando funciones (operadores) sobre los mismos. ▣ RxJava se basa en los Observables de .NET 4 (año 2010).
Observer ▣ Define una única interfaz común para todos los objetos que se suscriben a Observables. ▣ Dispone de tres métodos: □ onNext() Llamada con cada elemento emitido. □ onError() Para situaciones de error. □ onCompleted() El procesamiento acabó correctamente.
Creación de Observable ▣ La creación se hace (preferentemente) a través de métodos estáticos de Observable. □ Observable.just(elemento) □ Observable.just(elemento 1, elemento 2, ...) □ Observable.from(iterable) □ Observable.interval(3, TimeUnit.SECONDS)
Operadores sobre el stream ▣ Hay cientos de operadores aplicables sobre un stream. ▣ Se pueden definir operadores propios, aunque es un caso poco habitual. ▣ Con ellos, podemos: □ Modificar eventos del stream y reemitir la versión modificada. □ Filtrar elementos, para que no continúen downstream. □ Combinar elementos. □ ¡Multithreading!
Operadores sobre el stream ▣ Hay cientos de operadores aplicables sobre un stream. ▣ Se pueden definir operadores propios, aunque es un caso poco habitual. ▣ Con ellos, podemos: □ Modificar eventos del stream y reemitir la versión modificada. □ Filtrar elementos, para que no continúen downstream. □ Combinar elementos. □ ¡Multithreading!
Una nota sobre funciones lambda ▣ RxJava funciona sobre Java6, es decir, Android 2.3. ▣ Java no soporta funciones lambda hasta Java 8. ▣ Para ello, yo utilizo Retrolambda, que permite utilizar dichas funciones compiladas con bytecode Java 6.
Cuidado! ▣ Bytecode de la versión con new Func1 iconst_1 invokestatic java/lang/Integer valueOf((I)Ljava/lang/Integer;); invokestatic rx/Observable just((Ljava/lang/Object;)Lrx/Observable;); new anpez/Test$1 dup aload0 // reference to self invokespecial anpez/Test$1 ((Lanpez/Test;)V); .map(x -> x*x)
subscribeOn vs observeOn ▣ Estos dos operadores especiales se utilizan para indicar a RxJava en qué hilos se ha de ejecutar cada paso. ▣ subscribeOn() indica en qué hilo se han de emitir los eventos. ▣ observeOn() especifica en qué hilo se ejecuta el .onNext(). ▣ Por defecto, todo ocurre en el mismo hilo en que se suscribe el Observer.
Un ejemplo completo (I) ▣ En este ejemplo cargamos una imagen de red. Observable.just(url) .map(url -> loader.load(url)) .subscribe(ImageView::setBitmap);
Un ejemplo completo (II) ▣ Ahora la carga se produce en un hilo de I/O. Observable.just(url) .map(url -> loader.load(url)) .subscribeOn(Schedulers.io()) .subscribe(ImageView::setBitmap);
Un ejemplo completo (y III) ▣ De este modo, la vista se toca en el hilo correcto. Observable.just(url) .map(url -> loader.load(url)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(ImageView::setBitmap);
Retrofit ▣ Con Retrofit podemos obtener un Observable a partir de una llamada. public interface ChecksService { @GET("checks") Observable> all(); @POST("request") Observable issue(@Body Map body); }
Retrofit ▣ Y podemos aplicarle los operadores que queramos para transformar el resultado. public Observable issue(Check check) { Map params = new HashMap<>(); params.put("check", check.name()); return checksService.issue(params) .map(IssueResponse::issued) .map(time -> time*1000) .map(Date::new); }
RxBinding ▣ Librería de Jake Wharton para utilizar RxJava sobre vistas de Android. ▣ Elimina la necesidad de utilizar listeners. ▣ Simplifica operaciones muy comunes. RxTextView.textChanges(hostTextView) .subscribe(this::checkHost);
@Mock HostsRepository hostsRepository; Host host = new Host(1, "[email protected]", "https", "test.com", 443, "user", "pass"); when(hostsRepository .all()) .thenReturn(Observable.just(host)); Test! ▣ RxJava también te ayuda a escribir tests. ▣ Utilizando Espresso con Mockito, es muy fácil mockear un método que devuelve un Observable.
Gotchas ▣ Es muy importante definir un .doOnError() que recoja los posibles errores. ▣ Los Observers deben poder procesar eventos más rápido de lo que los emiten los Observables (Backpressure). □ No suele ser un problema, hasta que intentamos hacer algo avanzado. ▣ NO utilizar Observable.create(). Si usamos RxJava2, utilizar Flowable.create().
Gotchas ▣ No usarlo ciegamente, implica una penalización de rendimiento. ▣ La documentación a veces no es demasiado clara. □ Los comentarios de StackOverflow tampoco suelen ayudar mucho. ▣ Los tutoriales que verás, suelen utilizar incorrectamente Observable.create().
Conclusiones ▣ Se puede usar RxJava para reemplazar completamente las AsyncTask. ▣ Por norma general, se puede usar RxJava (via RxBinding o hecho por nosotros mismos), en lugar de Listeners. ▣ Para tareas de entrada/salida o de computación es perfecto, ya que la sobrecarga es mínima en este caso.