Introducción a RxJava en Android

5
24264

En este tutorial se hace una introducción a RxJava, librería que trata de facilitar la programación asíncrona y basada en eventos mediante el uso de Observables.

Índice de contenidos

1. Introducción a RxJava en Android

RxJava es una librería que trata de facilitar la programación asíncrona y basada en eventos mediante el uso de Observables. Se basa en el patrón Observer al que le añade operadores que nos permiten componer distintos Observable además de ayudarnos a abstraernos de problemas cómo threading y sincronización.

RxJava es una librería ideal para usar en Android, ya que para conseguir una buena experiencia de usuario recomiendan hacer todas las operaciones en un hilo en background, lo que obliga a todos los desarrolladores de Android a lidiar con problemas de concurrencia y asincronía.

Por ello RxJava se presenta cómo la alternativa perfecta a otras abstracciones comúnmente usadas en Android como AsyncTask, la cual presenta varios problemas cómo son la falta de composability (capacidad de componer distintas unidades), memory leaks y diferencias de funcionamiento en las distintas versiones de Android. Además RxJava se integra con otras librerías ampliamente usadas en Android cómo Retrofit.

2. Bases para entender RxJava

RxJava se sostiene sobre dos tipos Observer y Observable, el Observer se subscribe al Observable reaccionando ante los elementos que emite el Observable. El Observer no bloquea ningún hilo mientras espera a que el Observable emita elementos.

2.1. Observer

public interface Observer<T> {
    void onCompleted();
    void onError(Throwable e);
    void onNext(T t);
}

Los Observer cuentan con estos tres métodos:

  • onCompleted notifica al Observer de que el Observable ha terminado de mandar elementos.
  • onError notificará al Observer de que se ha producido algún tipo de error.
  • onNext provee al Observer con nuevos elementos.

2.2. Observable

Los Observable son mucho más complicados que los Observer, por lo que solo vamos a repasar algunos de los métodos más comunes y más adelante veremos las diferentes maneras que tenemos de crear un Observable y empezar a emitir elementos con él. La clase Observable tiene la siguiente signatura:

public class Observable<T> {
    ... 
}

Algunos de los métodos más relevantes de está clase son (podemos ver la lista completa aquí):

  • observeOn y subscribeOn, especifica el hilo en el que se ejecutarán el Observable y el Observer, debido a cómo funciona Android debemos indicar en que hilo queremos que se ejecuten ambos. Normalmente querremos que el Observable realice su trabajo en un hilo en segundo plano y después notifique al Observer en el hilo principal para que este pueda actualizar la Ui. subscribeOn se refiere a donde se va a ejecutar el Observable mientras que observeOn indica donde se va a ejecutar el código del Observer
  • doOnNext, doOnError y doOnComplete, esto permite registrar una acción que se invocará en los diferentes puntos. Un uso interesante de esto es si quisiéramos añadir ciertas acciones a hacer y después devolver el Observable, de manera que para quien consuma ese Observable estás opciones son transparentes.
  • doOnSubscribe y doOnUnsuscribe, esto nos permite añadir acciones cuando un Observer se subscribe.
  • subscribe este es el método que une el Observer con el Observable, ya que para que el Observer pueda empezar a recibir elementos emitidos por el Observable este tiene que subscribirse.

2.3. Operadores

Además de estos de estos tipos, existen una serie de operadores que actuaran sobre los diferentes elementos emitidos por el Observable que nos permitirán transformar, agrupar y filtrar los elementos emitidos. Estos elementos suelen seguir la nomenclatura que existe en otros lenguajes funcionales cómo map, filter o zip, puedes encontrar la lista completa aquí.

Un ejemplo de transformación sería la de map:

rxjava_android_map

Aplicará la función a todos los elementos individualmente, en el caso de la imagen los multiplica por 10.

rxjava_android_filter

En el caso de filter, solo emitirá los elementos que cumplan la función ó predicado, en el caso de la imagen emitirá los elementos que sean mayores de 10.

3. RxJava en la práctica

Una vez hemos visto los conceptos básicos de RxJava vamos a ver su uso en la práctica, viendo como se realizarían algunas tareas básicas en Android, cómo pueden ser:

  • Realizar una petición de red en un hilo en segundo plano y notificar al hilo principal (UI) para mostrar por pantalla parte de esa información.
  • Vamos a ver cómo encadenar varias peticiones
  • Usar algunos operadores para mostrar cómo se puede convertir la información que nos devuelve de manera declarativa.
  • Convertir un API ( cómo la gran mayoría de las APIs de Android ) que funcione mediante callbacks a RxJava.

3.1. Hacer una petición a red y notificar al hilo principal

Este es uno de los principales usos de concurrencia en Android y a la vez uno de los que más problemas suelen ocasionar debido al uso de los AsyncTask. En el ejemplo vamos a ver primero cómo realizar una llamada a un servicio Rest que nos devolverá un Array de String el cual es una lista de los ID de dichos elementos, primero mostraremos ese id y a continuación vamos a ver cómo enlazar peticiones para obtener cada elemento a partir del id.

Para realizar la petición HTTP vamos a usar Retrofit, el cual nos hace la mitad del trabajo a la hora de realizar la petición, nosotros no vamos a tener que preocuparnos de cómo se hace, si no que nos va a devolver un Observable<T> donde T es el POJO que representa el JSON que devolverá el servicio, nosotros solo tenemos que decirle en que hilo queremos que se realice la petición y que queremos hacer con la información que nos devuelve.

public class RestClient {

    protected interface HNService {
    
        @GET("/newstories.json")
        public Observable<NewStories> getNewStories();
        
    }

    private static HNService hnService;

    public static HNService getHNService() {
        if (RestClient.hnService == null) {
            RestAdapter retrofit = new RestAdapter.Builder()
                    .setEndpoint("https://hacker-news.firebaseio.com/v0")
                    .build();

            RestClient.hnService = retrofit.create(HNService.class);
        }

        return RestClient.hnService;
    }

    private static class HNItem {
        private String by;
        private int descendants;
        private String id;
        private int[] kids;
        private int score;
        private long time;
        private String title;
        private String type;
        private String url;
    }

    protected static class NewStories extends ArrayList<String>{}
}

Este sería el código de nuestro cliente rest, si no conoces Retrofit puedes echarle un vistazo a la documentación oficial, lo único que nos interesa saber es que nos devuelve un Observable<NewStories>, el cual ejecutará la petición en el momento en el que añadamos un Subscriber.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.listFragment = (ListFragment) getSupportFragmentManager().findFragmentById(R.id.listFragment);

    RestClient.getHNService()
        .getNewStories()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe((newStories) -> {
                ArrayAdapter<String> adapter =
                        new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
                adapter.addAll(newStories);
                listFragment.setListAdapter(adapter);
            },
            e -> e.printStackTrace());
}

En los ejemplos usaremos retrolambda que nos permite el uso de Lambdas en Android

Este sería el código de nuestra actividad, vamos a usar un ListFragment que encapsula un ListView en un Fragment y un sencillo ArrayAdapter para rellenar la lista con los datos que nos devuelve el servicio. En este ejemplo le estamos indicando con subscribeOn que queremos que el Observable se ejecute en un hilo en segundo plano, y con oberveOn que queremos que el código del Observer (en este caso las dos lambdas que le pasamos al método subscribe) se ejecuten en el hilo principal.

rxjava_android_list

3.2 Encadenar peticiones usando RxJava

Es bastante común el hecho de tener que encadenar varias peticiones, en la cual la segunda petición requiere información de la anterior y así N peticiones. En este caso, vamos a usar los id de las historias que hemos obtenido en el ejemplo anterior para obtener el detalle de cada historia, para eso tendremos que realizar una petición por cada historia.

hnService.getNewStories()
         .subscribeOn(Schedulers.io())
         .map(newStories -> getItemObservables(hnService, newStories))
         .flatMap(Observable::merge)
         .doOnNext(hnItem1 -> Log.d(TAG, "item received " + hnItem1.id))
         .observeOn(AndroidSchedulers.mainThread())
         .subscribe(this::addItemToList,
                Throwable::printStackTrace);
                    
private void addItemToList(RestClient.HNItem hnItem) {
    this.adapter.add(hnItem.title);
}

private List<Observable<RestClient.HNItem>> getItemObservables(RestClient.HNService hnService, RestClient.NewStories newStories) {
    return Lists.transform(newStories, hnService::getItem);
}

Para ello, vamos a usar el operador Map, que aplica una función a todos los elementos de la lista en este caso la conversión que vamos a hacer va a ser de un Array de Strings a un Array de Observables de HNItem, después usamos el operador Merge que convierte un array de Observables en un solo Observable que emite los elementos individualmente, y después añadimos un Subscriptor que consumirá los HNItem que vaya generando el Observable.

3.3 Convertir en reactiva una API basada en callbacks

Muchas de las APIs de Android funcionan mediante Callbacks, que en Android son implementadas normalmente usando clases anónimas, un ejemplo de esto sería añadir un evento cuando el usuario pulse en un elemento de la lista:

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Log.d(TAG, "Item at position " + position + " pressed");
    }
});

Para convertir este tipo de eventos a RxJava debemos crear un Observable que añada el listener cuando un Observer se suscriba, el código quedaría así:

private Observable<Integer> getOnListItemClickObservable() {
    return Observable.create(subscriber -> {
        listFragment.getListView().setOnItemClickListener((parent, view, position, id) -> {
            if (!subscriber.isUnsubscribed()) {
                subscriber.onNext(position);
            }
        });
    });
}

y podríamos empezar a consumir los elementos que emita este Observable igual que hemos hecho con los otros Observable:

this.getOnListItemClickObservable()
        .doOnNext(position -> Log.d(TAG, "Item at position " + position + " pressed"))
        .subscribe();
        

Para esta misma tarea, el proyecto RxBinding nos ofrece una serie de métodos que nos permiten crear Observables de una manera sencilla, por lo que no haría falta que creásemos nuestros propios Observables.

RxAdapterView.itemClicks(listView)
        .subscribe(position -> Log.d(TAG, "Item at position " + position + " pressed"));
        
        

Convertir este tipo de API’s en reactivas a veces es necesario cuando queremos introducir RxJava en aplicaciones legacy, usando esta técnica podemos hacer pequeños wrapper sobre partes antiguas y mezclarlas con partes totalmente nuevas, facilitando así la transición.

4. Conclusiones

RxJava es una herramienta que tiene mucho potencial, pero requiere un cambio de mentalidad a la hora de desarrollar, y para aquellos que hemos desarrollado siempre de manera imperativa cuesta al principio, pero una vez consigues hacerte a la idea de cómo funciona RxJava es fácil ver el gran potencial que tiene.

El código usado en este tutorial está subido a un proyecto en Github

5 COMENTARIOS

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad