El pronóstico del tiempo con RxAndroid y Retrofit

Hoy en día la programación reactiva está en boca de todos. Si bien a veces estas modas no son más que eso, otras son un descubrimiento increíble como por ejemplo RxJava y la versión que nos ocupa en este post, RxAndroid.

Para quienes no hayan escuchado nunca sobre la programación reactiva, se podría definir como “programación asíncrona con streams de datos”. Si, esto no es nada nuevo, seguramente ya lo hayas hecho antes cuando has implementado un click de un botón o si has trabajado con buses de eventos. Aun así, la programación reactiva representa esa misma idea pero muy potenciada, donde prácticamente cualquier elemento puede ser un “stream”.

A parte de reaccionar a eventos, la programación reactiva está basada en la programación funcional, donde el “stream” de salida de una función puede ser la entrada para otra función, puede ser filtrada, mapeada, etc.

 

Una vez hecha la introducción, lo primero que te pasará por la cabeza es “¿Por qué debería de adoptar la programación reactiva?”. Pues entre los motivos de mayor peso podrás encontrar el hecho de que el código escrito de manera reactiva es más conciso y con un nivel de abstracción mayor, evitando largos “chunks” de código de implementaciones. Por otro lado, las aplicaciones hoy en día contienen cantidad de eventos en tiempo real, mediante la programación reactiva puedes encadenar de manera sencilla dichos eventos para obtener una mejor experiencia de usuario.

Dicho esto, es hora de ponerse manos a la obra.

 

Creación del proyecto

Lo primero que haremos será crear un proyecto básico con Android Studio. Para ello seguimos el “Wizard” que implementa el IDE.

 

Configuración previa

Lo primero que haremos será agregar las librerías necesarias para poder trabajar con RxAndroid y Retrofit, así como alguna otra para facilitarnos el trabajo a la hora de realizar el proyecto.

Para ello vamos a modificar 2 ficheros. El build.gradle del proyecto y el del módulo app.

En el primero de ellos añadimos las siguientes líneas:

classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'me.tatarka:gradle-retrolambda:3.2.3'

En el segundo agregamos las librerías, importamos los plugins y habilitamos el soporte para Java 8

apply plugin: 'me.tatarka.retrolambda'
…
android {
    … 
    dataBinding {
        enabled = true
    }

    //enable java8 support
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
   }
}

//lambdas support
retrolambda {
    javaVersion JavaVersion.VERSION_1_7
}

def SUPPORT = "25.3.1"
def RETROFIT = "2.3.0"
def OKHTTP = "3.8.1"

dependencies {
   …
   compile "com.squareup.retrofit2:retrofit:$RETROFIT"
   compile "com.squareup.retrofit2:converter-gson:$RETROFIT"
   compile "com.squareup.retrofit2:adapter-rxjava2:$RETROFIT"
   compile "com.squareup.okhttp3:okhttp:$OKHTTP"
   compile "com.squareup.okhttp3:logging-interceptor:$OKHTTP"
   compile 'com.google.code.gson:gson:2.7'
   compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
   compile 'io.reactivex.rxjava2:rxjava:2.1.0'
   compile 'com.github.bumptech.glide:glide:3.8.0'
}

Hablando con la API

Para poder entablar conversación con la API que nos devolverá el pronóstico del tiempo, necesitamos una clase que genere servicios y se los asigne a Retrofit. Pese a que solo tendremos un único servicio, es una buena práctica prever y adaptar el código para un futuro.

RxWeatherService.java

import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface RxWeatherService {
    @GET("forecast.json")
    Observable<Weather> getWeather(
            @Query("key") String key,
            @Query("q") String city
   );
}

Nuestra petición de Retrofit nos devolverá un Observable al que nos suscribimos para obtener los resultados, el cual nos devolverá un “stream” de tipo Weather.

ServiceGenerator.java

import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class ServiceGenerator {

    private static HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY);

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder()
            .addInterceptor(interceptor);

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(BuildConfig.URL_ENDPOINT)
                    .addCallAdapterFactory(
                            RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
                    )
                    .addConverterFactory(GsonConverterFactory.create());

    public static <S> S createService(Class<S> serviceClass) {
        builder.client(httpClient.build());
        Retrofit retrofit = builder.build();
        return retrofit.create(serviceClass);
    }
}

Hemos agregado un interceptor al cliente de OkHttp para poder ver los datos que se envían y se reciben en las peticiones http que realicemos. También hemos definido el pool de hilos que usará RxAndroid para ejecutar las peticiones de Retrofit. En nuestro caso Schedulers.io() para tareas de input / output o networking.

A parte de Schedulers.io() existen varios pools más:

  • Schedulers.computation() (para tareas con alto uso de CPU).
  • Schedulers.single() (un único hilo que responderá a todas las unidades de trabajo asignadas).
  • Schedulers.newThread() (devuelve un Scheduler que creará un nuevo hilo por cada unidad de trabajo que se le asigne).
  • Schedulers.from(Executor executor) (crea un scheduler en un Executor específico).
  • Schedulers.trampoline() (se ejecuta en el mismo hilo, aunque es bloqueante).

 

Realizando la petición Http

Una vez tenemos el servicio y el generador creados es hora de realizar la llamada a la API desde nuestra MainActivity.java.

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private final static String TAG = MainActivity.class.getSimpleName();
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        doGetBilbaoWeather();
    }

    private void doGetBilbaoWeather() {
        ServiceGenerator.createService(RxWeatherService.class)
                .getWeather(BuildConfig.APIXU_KEY, "Bilbao")
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(weather -> {
                            binding.setCurrent(weather.getCurrent());
                            binding.setLocation(weather.getLocation());
                            initListView(weather);
                        }
                        , throwable -> throwable.printStackTrace()
                        , () -> Log.d(TAG, "Completed"));
    }

    private void initListView(Weather weather) {
        final RecyclerView list = binding.list;
       list.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
        final WeatherAdapter adapter = new WeatherAdapter(
                weather.getForecast().getForecastdays().get(0).getHours());
        list.setAdapter(adapter);
    }
}

Ahora, analicemos por partes nuestra petición:

  • ServiceGenerator.createService(RxWeatherService.class) con esta línea recuperamos el servicio a través de nuestro generador de servicios con los clientes inicializados.
  • .getWeather(BuildConfig.APIXU_KEY, «Bilbao») es la petición que vamos a realizar, en este caso un GET al que se le pasan la key de Apixu para obtener los pronósticos y la ciudad de la que queramos obtener los resultados. En este punto, la función nos devolverá el Observable que hemos designado anteriormente en la definición de RxWeatherService.java
  • Con dicho observable lo que haremos es definir en qué hilo queremos los resultados de la request, en este caso le indicamos que queremos el main thread de Android usando la siguiente línea .observeOn(AndroidSchedulers.mainThread())
  • Una vez tengamos toda la función preparada, le decimos que nos queremos suscribir con .subscribe al cual le indicamos que queremos trabajar con su resultado (weather -> {}), podemos también registrar la excepción en el caso de que la haya (throwable -> {}) e incluso mandar un log del final del evento ( () -> {}).

Una vez hayamos tratado los resultados obtenidos en la función «weather -> {}«, mandaremos dichos resultados a la UI, en nuestro caso hemos optado por hacer uso del Data Binding, aunque podéis hacerlo de la manera tradicional si preferís.

 

Aplicación de pronóstico de tiempo con RxAndroid y Retrofit

RxWeather App

Podéis encontrar el código en el siguiente enlace a Github así como su versión en Kotlin por si queréis adentraros un poco en lo nuevo de Google y Android.

Recordad que el código lo podéis modificar, compartir e incluso podéis contribuir al repositorio original. Esta sencilla aplicación puede dar pie a muchas mejoras para que tengáis siempre el pronóstico del tiempo en vuestro bolsillo ;).



¿Te gusta este post? Es solo un ejemplo de cómo podemos ayudar a tu empresa...
Sobre Asier Fernandez Rodero

Android OS developer at @irontec. Cars&Coffee fan. #LIFEISGOOD! No snowflake in an avalanche ever feels responsible. - Stanislaw Jerzy Lec

Queremos tu opinión :)