Apollo + Dagger2 | GraphQL in Android (Part 1)

Carlo Huamán
OrbisMobile
Published in
11 min readApr 12, 2018

Una de las cosas con las que siempre me he topado al querer implementar una librería, es la forma en la que muchos usuarios tratan de implementarla; desde la más simple, la más compleja, la trabajosa o la que decimos aquí “la que funcione”.

Algo parecido me pasó la primera vez que quise implementar Dagger2, encontré mucha documentación, ejemplos, formas, pero al final, pude hacerme una buena idea del concepto de Inyección de Dependencias y su implementación en Android.

Pero luego vino Apollo, un cliente para poder usar GraphQL en distintas plataformas, como Android 😁. Muchas formas de implementarlo, distintos puntos de vista, etc. Entonces me dije: Apollo, Dagger2 . . . Why not both?

Fuente: Imgur — FolkingAwesome

No ahondare en el concepto de qué es GraphQL o Dagger2 o que es inyección de dependencias, creo que hay muchas otras mejores fuentes donde el concepto puede ser explicado al detalle (dejaré referencias). Mas si haré una demostración de cómo implementar Apollo en Android junto con Dagger2 y lo requerido para todo ello. Bueno, ¡Empecemos!

Proyecto de Ejemplo

He creado un proyecto en Github, lo que hace es consumir el API GraphQL de GitHub, basándome en el mismo ejemplo del GitHub de Apollo para Android, pues al mismo tiempo quiero hacer una comparación de cómo podemos tener un poco más ordenado y limpio nuestro proyecto. El proyecto tiene únicamente un FeedActivity que listará usuarios y sus repositorios. El proyecto está construido bajo el patrón MVP, en donde la actividad inyecta su respectivo presentador y este a su vez su interactor, todo bajo un contrato que me permite agregar o retirar métodos de cada uno de estos.

La implementación de Apollo está en un módulo dentro del paquete “di” (Dependency Injection) y el consumo del API está en el interactor FeedInteractor.

Estructura del Proyecto

El paquete app contiene la clase GraphApplication, clase encargada de implementar la clase base Application de Android, teniendo un control del estado global de la aplicación. Fuente: Android Developer(Application)

En el paquete di encontraremos todas las clases necesarias para gestionar nuestra inyección de dependencias. Separadas respectivamente por paquetes como component, module. Anotations no es un paquete que se deba usar en proyectos de di, es solo costumbre mía para clases de tipo anotación que usaré en Dagger, pudo llamarse scopes o rules 😆.

El paquete ui/feed contendrá toda la pantalla Feed, si tuvieses otras pantallas como Splash or FeedDetail, podrías separar paquetes dentro de ui como ui/feed, ui/splash y ui/detail. He visto ejemplos donde todos los adapters están en un paquete “adapter”, los presentadores están en un paquete “presenter”, los “interactors” en otro paquete del mismo nombre y así sucesivamente. Particularmente no digo que este mal, sin embargo prefiero manejar cada adapter, presenter, interactor o contrato en el paquete respectivo a su vista, de esa forma se puede hacer mantenible una sola vista cuando quieres tratar solamente con ella. Pero esto es sólo una opinión.

Setup

Las librerías que usaremos para Apollo y Dagger son las siguientes:

//Apollo dependencies
implementation ‘com.apollographql.apollo:apollo-runtime:0.4.4’
implementation ‘com.apollographql.apollo:apollo-android -support:0.4.4’
//Http Interceptor
implementation ‘com.squareup.okhttp3:logging-interceptor:3.9.1’
//Dagger dependencies
implementation ‘com.google.dagger:dagger:2.13’
implementation ‘com.google.dagger:dagger-android:2.13’
implementation ‘com.google.dagger:dagger-android-support:2.13’
annotationProcessor ‘com.google.dagger:dagger-compiler:2.13’
annotationProcessor ‘com.google.dagger:dagger-android-processor:2.13’

Cabe indicar que la librería de Http Interceptor no es necesaria para que Apollo funcione, es sólo para monitorear el tráfico de tus consultas.

Dicho todo esto, empezaré por todo lo usado para la inyección de las dependencias necesarias para el ejemplo:

Modules

Básicamente tenemos tres módulos para nuestra aplicación (En realidad son 4 pero hablaré del restante más adelante); que son GraphModule y ActivityModule y FeedActivityModule (esté ultimo está en un paquete distinto). El concepto de módulos como tal lo pueden encontrar en su documentación justo aquí.

GraphModule

En éste módulo construiremos una función que retorne a Dagger el Apollo Client, junto con el cliente de OkHttp como mencioné anteriormente para monitorear el tráfico.

provideInterceptor() proveerá un Loggin Interceptor, que permite configurar el nivel del monitoreo de las peticiones, sea a un nivel Body o None, tal cual hemos trabajado muchos con Retrofit. Esto con la ayuda de una variable de entorno BuildConfig.DEBUG usando short-if.

provideOkHttpClient() nos provee el cliente OkHttp que será configurado con el interceptor que previamente configuramos.

provideApolloClient() proveerá un Apollo Client, que recibe como parámetro nuestro cliente OkHttp ya con toda la configuración necesaria para monitorear nuestras peticiones. En este método se configura tanto como el cliente OkHttp así como la Url base de nuestras peticiones.

ActivityModule

Así como el módulo GraphModule provee el Apollo Client a Dagger para su posterior consumo, en ActivityModule haremos lo mismo pero proveeremos a Dagger las actividades que inyectarán sus respectivos módulos más adelante. De esa forma, dagger conocerá nuestras actividades en tiempo de compilación. En esta aplicación tenemos una sola actividad por lo que si se tuviese más actividades, estas irían aquí mismo.

¿Y por qué tenemos que inyectar nuestras actividades de esta forma?. Seguramente te has topado con algunos ejemplos donde las actividades se inyectan de una forma mas o menos así:

DaggerActivityComponent.builder()
.someModule(new SomeModule(this))
.otherModule(new OtherModule())
.build()
.inject(this);

El anterior bloque de código, era declarado en el onCreate() de nuestras actividades, permitiendo así inyectar los módulos correspondientes a esa actividad, al menos era así antes de Dagger 2.10. ¿Cuál es el problema con esto si ya funcionaba bien?. Que rompe el principio básico de la inyección de dependencias.

Una clase no debería saber nada acerca de cómo es inyectada.

Esto último se lo agradezco a un post magnífico acerca del Android Inyector que pueden encontrar aquí.

FeedActivityModule

Antes mencioné que el paquete di/module contendría todos los módulos de la aplicación. Sin embargo éste módulo lo tengo separado de los otros porque, imaginen si tuviéramos al menos 8 actividades, nuestro paquete module estaría repleto de ellos, sin contar con los módulos generales (como es el caso de GraphModule), en cambio este módulo, al estar junto con su respectiva vista, me permite tener un mejor control de él y su relación con la vista, en este caso FeedActivity.

A diferencia del GraphModule, observamos que este módulo es de tipo abstracto, tiene sus métodos Provide y una etiqueta llamada Binds que su método, igual que la clase, es abstracto.

Binds es una etiqueta de dagger que cumple la misma función que Provide, pero a diferencia de esta, es usada para funciones que únicamente devuelven el parámetro inyectado, como es el caso de nuestro provideFeedView.

Por anotación, Binds siempre debe ser de tipo abstracto, y si nuestro método es abstracto, su clase padre también debe serlo.

Observamos que este módulo tiene la función de proveer tanto la vista, el presenter y el interactor de nuestra actividad (MVP), y si bien observamos que el interactor recibe nuestro Apollo Client que será provisto por el GraphModule, también recibe un parámetro adicional provisto por el método provideMainHandler(), un Handler, pero, ¿Por qué necesitamos un Handler?

@Provides
static Handler provideMainHandler() {
return new Handler(Looper.getMainLooper());
}

Si vamos a la documentación de Apollo Client, vemos que esta nos dice que todas las respuestas de Apollo vienen en un hilo secundario, distinto al principal de la aplicación, entonces necesitamos una forma de traer esa data al hilo principal de la aplicación para poder pintar correctamente la data obtenida y no tener ningún crash en el camino 😵.

En este ejemplo usaremos dos formas de traer la data, uno es con el Handler, que lo instanciaremos con el looper principal de la aplicación por medio de la clase Android Looper, información que pueden verificar aquí; y otra es con un método runOnUiThread, ambos los usaremos más adelante.

DaggerApplication & DaggerAppCompatActivity

En el caso de nuestra clase “Application” donde a través de un HasActivityInjector, podíamos indicarle a Dagger la inyección de actividades, antes de Dagger 2.10, teníamos algo parecido a esto:

Pero podemos reducir todo ello a algo más corto extendiendo a nuestra clase aplicación de DaggerApplication en lugar de Application.

¿Más simple verdad?

Lo mismo pasa en el caso de la actividad, antes inyectábamos más o menos así:

DaggerActivityComponent.builder()
.someModule(new SomeModule(this))
.otherModule(new OtherModule())
.build()
.inject(this);

O también así:

AndroidInjection.inject(this);

Ahora con la ayuda de DaggerAppCompatActivity, sólo hacemos que nuestra actividad extienda de esta clase y ¡listo!. Ya no es necesario agregar nada más 😀.

Literalmente no fue necesario agregar nada más!!

Hasta ahora, hemos visto todo lo necesario para poder inyectar las clases necesarias para consumir Apollo desde el punto de vista de Dagger pero, ¿Dónde queda GraphQL?, pues ahora vamos a ese punto!

Fuente: Tenor

Muchas, pero muchas líneas de texto atrás 😆, mostré un apartado llamado Estructura del Proyecto, donde vimos la distribución de paquetes del proyecto, en especial los referidos para la inyección de dependencias. Ahora para continuar, debemos revisar un poco de lo necesario para poder usar GraphQL a través de su cliente Apollo Client.

Paquete “graphql”

Para generar consultas en GraphQL necesitamos básicamente dos cosas, nuestras queries a realizar al servidor en archivos *.graphql y nuestro archivo schema.json. Este archivo que contendrá todo lo que es posible retornar al servidor, así como las reglas, limitaciones o permisos de estos. Dicho de otra forma, un diccionario de lo que podemos y no podemos consultar, si quieren más información sobre schema.json o como crearlo puede visitar el siguiente link. Para evitar conflictos con el código, el paquete siempre debe estar fuera del paquete Java y estar al mismo nivel de este, como puedes apreciar la imagen.

Revisemos un poco nuestro archivo GitFeedQuery.graphql y veamos que contiene.

query FeedQuery($type: FeedType!, $limit: Int!) {
feedEntries: feed(type: $type, limit: $limit) {
id
repository {
...RepositoryFragment
}
postedBy {
login
}
}
}

fragment RepositoryFragment on Repository {
name
full_name
owner {
login
}
}

El primer bloque representa a nuestra query a realizar y los parámetros que deseamos que devuelva, bajo el nombre de FeedQuery. Esto es posible gracias a su palabra reservada “query”, vemos también que recibe dos parámetros: FeedType y un Limit.

Más abajo en el segundo bloque vemos una palabra fragment, seguida del nombre de ese bloque RepositoryFragment. Los fragmentos en GraphQL nos permiten reutilizar queries que necesitemos en nuestras consultas, de tal forma que nos permite tener un mayor orden en estas. Más información de fragments aquí. No tiene nada que ver con los fragments de tu aplicación, creanme al principio los confundí 😞.

Después de tener nuestras queries y schema.json listos, hacemos un build a la aplicación para que las clases que proveerán la data se generen automáticamente gracias a Apollo.

FeedInteractor

En este interactor usaremos básicamente tres variables: un Apollo Client, nuestro Handler y un Apollo Call de tipo FeedQuery.Data; para poder hacer las peticiones al API.

Tanto el Apollo Client como el Handler son inyectados en el constructor del interactor, métodos que ya previamente hemos configurado.

Nuestro método getFeedFromApollo recibe dos parámetros: un limit (para determinar el tamaño del feed a traer), así como un callback (para poder responder al presenter cuando la petición esté lista).

Lo primero en nuestro método es preparar la query, con la variable FeedQuery, donde establecemos el límite y el tipo de Feed a traer, puede ser TOP, HOT y NEW. ¿Recuerdan el schema.json?. Si bien dijimos que nuestro archivo *.graphql definimos la query que deseamos, dijimos que el schema.json define que podíamos consumir. He aquí tenemos un ejemplo de ello, donde los tipos de feed que se pueden traer sólo son 3 tipos, los mencionados anteriormente.

Lo segundo es instanciar nuestro Apollo Call, que lo hacemos a través de la variable dataApolloCall, donde lo configuramos con el FeedQuery hecho el paso anterior y un ApolloResponseFetchers de tipo NETWORK_FIRST, existen otras configuraciones de políticas de caché disponibles de HttpCachePolicy, como sólo caché o ambos al mismo tiempo; pueden tener más información acerca de estas políticas en el siguiente link.

Y por último hacemos la llamada al servicio, donde al ejecutar el .enqueue de nuestro dataApolloCall. Observamos que nos da dos métodos para poder gestionar el estado de la respuesta: onResponse y onFailure; si hemos trabajado con Retrofit antes, esta parte nos resultará bastante familiar. En ambos casos, podemos ver que con la ayuda de nuestro callback, podemos notificarle al presenter de la respuesta obtenida.

Es importante observar que el handler usado aquí es para que la respuesta sea manejada en el hilo principal y salga del hilo secundario donde nació.

Recuerdan que al inicio del post, mencioné que ¿Había dos formas de hacerlo?. Una era el handler y la otra con el método runOnUiThread. El método en mención se ejecuta en la actividad, entonces si no necesitáramos el handler, nuestro interactor quedaría de la siguiente forma.

Sea el método que elijamos trabajar, ambos responderán a la actividad por medio del callback entre el presenter y el interactor. Entonces ahora nos trasladamos a nuestra actividad donde pintaremos los datos.

FeedActivity

FeedActivity sólo pintará los resultados a través de un adapter en un RecyclerView. Para ello recibirá su presenter por inyección. Veamos la clase.

Puntos importantes en esta actividad son el presenter inyectado y el método showResult().

@Inject
Lazy<FeedPresenter> presenter;

Como podemos ver el presenter no sólo es inyectado en la actividad, utiliza una clase Lazy. Cuando una clase es inyectada a través de Lazy, no se inicializará, aún cuando esté declarada o esté siendo inyectada, solamente se inicializará cuando se use a través de su método get(). Ya en su segundo llamado, el valor cargado ya estará en memoria. De está forma la aplicación es más rápida por cargar los datos realmente cuando son requeridos y no todos de golpe al inicio de esta.

El llamado a nuestro interactor lo hacemos en la siguiente línea, donde le indicaremos que retorne un feed de límite 10:

presenter.get().getFeed(10);

El resultado de la consulta, será retornado en el método showResult();

@Override
public void showResult(List<FeedQuery.FeedEntry> feedEntries) {

//With Handler way
feedAdapter.addData(feedEntries);

}

Pero hay un comentario que dice “Handler Way”, ¿Y sin Handler?

@Override
public void showResult(final List<FeedQuery.FeedEntry> feedEntries) {

//Without Handler way
FeedActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
feedAdapter.addData(feedEntries);
}
});

}

El método runOnUiThread del cual les hablé, ejecuta una acción específica sobre el thread que se esta ejecutando en un view del hilo principal, en este caso, nuestro RecyclerView.

¿Y ahora qué?

Con todo lo anterior, podemos correr nuestra aplicación y tener el siguiente resultado:

Listo!!! Eso fue todo amigos!!! 🙌

No digo que la forma en la que he trabajado el ejemplo sea “La forma definitiva” o sean “Las buenas prácticas”, siempre habrá una mejor forma de hacerlo.

Bonus!!!

¿Y si quisiera hacerlo en Kotlin?

Bueno en Kotlin cambian algunas cosas, más por el tema de Dagger y no tanto por Apollo, pero estas “pequeñas cosas”, las estaré publicando en la siguiente parte de este post.

Referencias

--

--

Carlo Huamán
OrbisMobile

#Jesus is the way, the truth and the life | Dad, Mobile Architect @ BCP, Assistant #GDE | 🚴🥭🍫🍊 | bio.link/tohure