Apollo + Dagger2 | GraphQL in Android (Part 2)

Carlo Huamán
OrbisMobile
Published in
7 min readApr 23, 2018

E n mi anterior post, hablé acerca de cómo integrar el cliente de GraphQL en Android (Apollo Client), usando la librería de inyección de dependencias Dagger2. Para ello use un proyecto de ejemplo escrito en Java:

Y bueno, aunque Android Studio ya te ayuda en muchas cosas entre Java y Kotlin, la solución no siempre es:

No me digas que no lo has intentado por que sé que sí

Entonces, cuando queremos trabajar con Kotlin, algunas cosas cambian en el proyecto y en este post mostraré como implementar Apollo Client con Dagger a través de Kotlin sin morir en el intento 😆.

Proyecto de Ejemplo

Igual que en el caso anterior, usaremos el API GraphQL de Github para nuestro ejemplo. El proyecto de ejemplo lo he subido a Github donde está disponible para que puedan clonarlo.

Estructura del Proyecto

Al igual que el proyecto anterior, mantendremos la misma estructura de paquetes para nuestra aplicación.

Contamos con un paquete app para nuestra clase Application.

Nuestro paquete di que contendrá todo lo respectivo para la inyección de dependencias.

Y por último nuestro paquete ui/feed con todo lo correspondiente a la vista Feed.

Setup

Los primeros cambios se darán en nuestro archivo gradle. En la versión de Java, Dagger utiliza los procesadores de anotación propios de Java.

//Annotation Processor
annotationProcessor ‘com.google.dagger:dagger-compiler:2.13’
annotationProcessor ‘com.google.dagger:dagger-android-processor:2.13’

Sin embargo Kotlin maneja los suyos propios: Kapt. Para implementarlo primero debemos llamar al plugin respectivo en gradle.

apply plugin: 'kotlin-kapt'

Luego de ello, sólo cambiamos todas nuestras anotaciones annotationProcessor por “kapt”, de tal modo que nuestro archivo gradle quedaría de la siguiente forma.

//Apolo 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'
kapt 'com.google.dagger:dagger-android-processor:2.13'
kapt 'com.google.dagger:dagger-compiler:2.13'

Modules

En el caso de los módulos, cambian la declaración de las funciones por las mismas propiedades de inferencia de Kotlin, por lo que mucho de nuestro código se verá reducido para bien 🙂.

GraphModule

Este módulo no ha cambiado en nada, salvo por un short if de Java que en Kotlin tiene que suplantarse por el if original. En Java:

#Java
BuildConfig.DEBUG
? HttpLoggingInterceptor.Level.BODY
: HttpLoggingInterceptor.Level.NONE

Y en Kotlin:

#Kotlin
if
(BuildConfig.DEBUG)
HttpLoggingInterceptor.Level.BODY
else
HttpLoggingInterceptor.Level.NONE

Obviamente palabras reservadas de Java como new, o formas de declarar las funciones, ubicación del tipo de parámetros, etc; eso si ha cambiado dado que la sintaxis de Kotlin es diferente a la sintaxis de Java. Como por ejemplo: en Java es necesario decir que una clase es public, mientras que en Kotlin eso ya es inherente.

#Java
@Module
public class GraphModule
#Kotlin
@Module
class GraphModule

ActivityModule

Bueno, en esta clase casi no hay cambio, todo igual 😇

#Java
@Module
public abstract class ActivityModule {

@ContributesAndroidInjector(modules = FeedActivityModule.class)
abstract FeedActivity bindFeedActivity();

}
#Kotlin
@Module
abstract class ActivityModule {

@ContributesAndroidInjector(modules = [(FeedActivityModule::class)])
abstract fun bindFeedActivity(): FeedActivity

}

FeedActivityModule

Aquí empiezan nuestros primeros cambios, empecemos por revisar como era nuestra clase en Java.

Lo primero a resaltar es que nuestros provides en Java, específicamente de esta clase, son de tipo estáticos, pero en las otras clases no lo eran, entonces. . . ¿Porqué son estáticos aquí?

error: A @Module may not contain both non-static @Provides methods and abstract @Binds or @Multibinds declarations

Dagger mismo nos indica que si ya tenemos una clase de tipo Bind en nuestro módulo, las demás deben ser de tipo “no estáticos”, así que cambiamos el tipo de nuestros Provides para que no entren conflicto con nuestro Bind, pero. . . ¿Y en Kotlin? Bueno, Kotlin no maneja estáticos como tal.

¡¡¡¡¿CÓMO QUE KOTLIN NO MANEJA ESTÁTICOS?!!!!

Tranquilos, no es que Kotlin no pueda usar los beneficios de las clases estáticas de Java, Kotlin los maneja de otra forma, pero antes de seguir, debemos entender porqué usamos clases de tipo static en Java.

Creamos una clase de tipo estática cuando queremos acceder a ella o sus métodos sin tener que crear una instancia de la misma. Fuente: Oracle, Java

Dicho lo anterior, ¿Cómo podría manejar esto en Kotlin?…. Companion objects

Miembros de un companion object, pueden ser utilizados simplemente con el nombre de la clase como calificador. Fuente: Kotlin, Companion Objects

Entonces si tengo al menos tres métodos provides y necesito tratarlos como estáticos, ¿Cómo quedaría mi módulo en Kotlin?

Al tener nuestro companion object, necesitamos indicarle a Kotlin que no sólo lo trataremos como un conjunto de funciones internas, si no como un módulo de Dagger, por ello también lleva la anotación Module. Al mismo tiempo observamos una anotación extra: JvmStatic. Sucede que por temas de interoperabilidad, si no usamos esta etiqueta, al querer tratarla desde Java, la clase tendría que hacer mención del companion.

¿Ah?

Veamos un ejemplo más claro de JvmStatic.

FeedActivityModule.provideMainHandler(); // con JvmStatic
FeedActivityModule.INSTANCE.provideMainHandler(); // sin JvmStatic

Recordemos que Dagger esta hecho bajo Java, lo que le traería problemas de legibilidad al tratar de leer un método estático de Kotlin sin JvmStatic. Si desean más información sobre la anotación JvmStatic pueden encontrarlo aquí.

DaggerApplication

Estas clases no han cambiado mucho, igual podemos ver como Kotlin trata las clases a diferencia de Java.

Java

public class GraphApplication extends DaggerApplication {

@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}

}

Kotlin

class GraphApplication : DaggerApplication() {

override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}

Paquete “graphql”

Como podemos observar, la configuración de nuestros archivos *.graphql y schema.json son las mismas. Mismos paquetes, misma distribución.

FeedInteractor

El cambio más significante en esta clase es la forma en que se inyecta las dependencias necesarias. Kotlin maneja su constructor de forma un tanto distinta que en Java, así que veamos más de cerca de que hablamos.

Java

public class FeedInteractor implements FeedContract.Interactor {

private final ApolloClient apolloClient;
private final Handler handler;
@Inject
FeedInteractor(ApolloClient apolloClient, Handler handler) {
this.apolloClient = apolloClient;
this.handler = handler;
}

}

Kotlin

class FeedInteractor
@Inject
constructor(private val apolloClient: ApolloClient, val handler: Handler) : FeedContract.Interactor {

}

Observamos que el primer cambio es en el constructor. En Java teníamos un constructor y por medio de el, inyectábamos las variables necesarias para trabajar. Sin embargo en Kotlin, hay distintas formas de manejar el constructor, con paréntesis o sin estos (un constructor default), pero en el caso de que necesitásemos usar anotaciones en nuestro constructor de Kotlin, es necesario usar la palabra reservada constructor. De esta forma Kotlin ya puede reconocer la etiqueta Inject en su constructor. Más info aquí.

Otro punto importante a mencionar es que a diferencia de Java, al anteponer la palabra reservada val en cada parámetro de nuestro constructor, no sólo tenemos una variable existente dentro del contexto del constructor, si no, una variable visible y utilizable por toda la clase.

FeedPresenter

El mismo caso que el anterior a nivel de constructores.

Kotlin

class FeedPresenter
@Inject
constructor(var view: FeedContract.View?, val interactor: FeedInteractor) :
FeedContract.Presenter,
FeedContract.Callback {

}

Java

public class FeedPresenter implements FeedContract.Presenter, FeedContract.Callback {

private FeedContract.View view;
private final FeedInteractor interactor;

@Inject
FeedPresenter(FeedContract.View view, FeedInteractor interactor) {
this.view = view;
this.interactor = interactor;
}

}

FeedActivity

En el caso de la actividad hay dos cambios principales que son el Inject del presenter y el método runOnUiThread.

En el caso del presenter teníamos algo así en Java:

@Inject
Lazy<FeedPresenter> presenter;

Pero si tratáramos de llevar este código directo a Kotlin van a pasar dos cosas, primero que Kotlin va a tratar de asignar un valor nulo (o inicializar la variable) y segundo que Kotlin te dirá que tiene que llevar el operador “?” por el tema de Null Safety. Más información aquí. Y aún haciendo todo esto igual Dagger te mencionará que existe un error por que una variable inyectada no puede ser nula. ¿Entonces? Pues nuestro buen amigo lateinit.

Lo sabía!!

La etiqueta Inject ya proporcionará el valor a nuestra variable de tipo presenter, pero Kotlin no sabe que Dagger hará eso, entonces, usamos la palabra reservada lateinit que le dirá a Kotlin, esta variable se inicializará más tarde, de tal modo que nuestro código quedaría de la siguiente forma.

@Inject
lateinit var presenter: Lazy<FeedPresenter>

Otra de las cosas que cambio fue nuestro método runOnUiThread, los cambios que se dan en Kotlin son por el tema de lambdas que ya se maneja de forma nativa (Aunque en Java 8 también se puede 😎). Entonces en Java teníamos un código de la siguiente forma:

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

En Kotlin se vería de la siguiente manera:

this@FeedActivity.runOnUiThread { 
feedAdapter.addData(feedEntries)
}

Aunque con Lambas en Java 8 también se ve igual de bonito 🤓

FeedActivity.this.runOnUiThread(() -> feedAdapter.addData(feedEntries));

Al correr el ejemplo, tendremos el mismo resultado que en nuestro proyecto anterior, sólo que ahora todo corre en Kotlin 😀.

Se que parece el mismo gif pero creanme, esta en Kotlin 😆

Referencias

--

--

Carlo Huamán
OrbisMobile

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