Imagen de cabecera

De RoboGuice a Dagger 2 – Parte 1

Conversión de RoboGuice a Dagger

Esta es la primera parte del tutorial de migración de RoboGuice a Dagger 2. Aquí tienes el enlace a las otras dos partes:

Introducción

Nuestra misión en esta primera parte será eliminar RoboGuice, convirtiendo los módulos de RoboGuice en módulos de Dagger.

Por suerte, RoboGuice utiliza módulos para hacer métodos provider y utiliza las mismas anotaciones (de paquetes distintos) aunque en lugares diferentes, para hacer singletons. Vamos a explicar todo lo que probablemente vayamos a encontrar en un módulo de RoboGuice y cómo convertirlo a la sintaxis de Dagger.

Este es un esquema de todos los pasos que tendremos que seguir para eliminar RoboGuice de nuestra aplicación y sustituirlo por Dagger:

  1. Convertir el módulo de RoboGuice a un módulo de Dagger
    1. bind(), bind().to() y bind().toInstance()
    2. .asEagerSingleton()
    3. TypeLiteral y annotatedWith(named())
  2. Revisar todas las clases
    1. Mover la anotación @Singleton
    2. Cambiar la anotación @Inject
    3. De Provider a Provider
    4. De RoboClases a Clases
  3. Crear el DIManager
  4. Eliminar el resto de basurilla
  5. Arrancar

¡Vamos a ponernos manos a la obra!

Tal vez te interese leer cómo se usan los módulos en RoboGuice. Aquí de todas formas lo explicaremos por encima para entender cómo reemplazarlos.

Tabla de conversiones

Este es un cuadro resumen de las conversiones que vamos a realizar:

RoboGuice Explicación Dagger 2
bind() Define un provider para una clase que crea una instancia utilizando el constructor vacío de esa clase o uno con objetos inyectables @Provides
bind().to() Define un provider para una interfaz @Provides
bind().toInstance() Define un provider para una clase que necesita ser configurada @Provides
.asEagerSingleton() Indica que la instancia generada sea única y que se cree en cuanto se inicialice RoboGuice @Singleton
TypeLiteral<> Devuelve una instancia de un objeto que utiliza un tipo genérico @Provides
.annotatedWith(named()) Diferencia a este provider de otros que devuelvan una instancia del mismo tipo que el suyo @Named
com.google.inject.@Provides Igual que en Dagger javax.inject.@Provides
@Named() Igual que en Dagger @Named()
com.google.inject.@Singleton Se pone marcando una clase para indicar que las instancias que se generen para inyectar deben ser siempre la misma javax.inject.@Singleton
com.google.inject.@Inject Se pone marcando los campos de una clase para indicar que son campos inyectados javax.inject.@Inject
com.google.inject.Provider Objeto utilizado para generar instancias de una clase javax.inject.Provider
RoboActivity Activity que inicializa la inyección de dependencias por defecto AppCompatActivity
RoboFragmentActivity FragmentActivity que inicializa la inyección de dependencias por defecto AppCompatActivity
RoboFragment Fragment que inicializa la inyección de dependencias por defecto Fragment
RoboDialogFragment DialogFragment que inicializa la inyección de dependencias por defecto Fragment
RoboService Service que inicializa la inyección de dependencias por defecto Service
RoboBroadcastReceiver BroadcastReceiver que inicializa la inyección de dependencias por defecto BroadcastReceiver
RoboSimpleAsyncTaskLoader AsyncTaskLoader que inicializa la inyección de dependencias por defecto AsyncTaskLoader

1. Convertir el módulo de RoboGuice a un módulo de Dagger

En RoboGuice un módulo es una clase que hereda de AbstractModule. Nuestro proyecto es una aplicación con una librería. Ambos usan RoboGuice. Esta es (una versión extremadamente simplificada de) la clase módulo de la librería:

Y esta es la de la aplicación:

Tenemos tema para rato. A ver por dónde le metemos mano. Lo primero que haremos será los módulos de Dagger: uno para la aplicación y otro para la librería:

Crea también los componentes. Eso no tiene mayor misterio.

bind(), bind().to() y bind().toInstance()

bind() indica que la instancia se construye con el constructor vacío de la clase o con uno que reciba parámetros que también sean inyectables. Por ejemplo bind(MobileSorter.class) indica que la instancia se construirá con el constructor vacío de la clase MobileSorter. Por lo tanto, sustituimos este código por un provider en el módulo de Dagger:

En este otro ejemplo bind(MobileManager.class) comprobamos la clase MobileManager y observamos que su constructor recibe una instancia de MobileSorter:

Esto quiere decir que para que funcione bind(MobileManager.class) tiene que haber un provider de MobileSorter, es decir, un bind(MobileSorter). Por lo que al convertirlo a Dagger, hemos de asegurarnos de que hay dos providers y que el de MobileManager recibe como parámetro un MobileSorter:

bind().to() es lo mismo que el anterior pero sirve para proporcionar instancias de interfaces. Así bind(PatchClientManager.class).to(PatchClientManagerImpl.class) indica que una instancia de PatchClientManager se creará mediante el constructor vacío de PatchClientManagerImpl o con un constructor que reciba parámetros inyectables. Por lo tanto lo sustituimos por un provider en el módulo de Dagger:

bind().toInstance() indica que la instancia que hay que devolver al pedir un objeto de esa clase debe configurarse, es decir, que no nos vale con el constructor vacío, sino que hay que generar la instancia de otra manera. Así, bind(Picasso.class).toInstance(Picasso.with(context)); indica que para obtener una instancia de Picasso hay que llamar a Picasso.with(context), lo cual devolverá la instancia necesaria. Por lo tanto, lo sustituimos por un provider en el módulo de Dagger:

.asEagerSingleton()

Este método hace que la instancia sobre la que se aplica sea única y que se cree en cuanto se inicialice RoboGuice (verdaderamente, que se inicialice con RoboGuice o que se inicialice la primera vez que la inyectemos nos es indiferente). Así bind(TagManager.class).to(TagManagerImpl.class).asEagerSingleton() devolverá una instancia única de TagManagerImpl creada mediante su constructor vacío. Por lo tanto, lo sustituimos por un provider con @Singleton en el módulo de Dagger:

TypeLiteral y annotatedWith(named())

TypeLiteral es un tipo de objeto que RoboGuice utiliza para poder devolver objetos tipados. Por ejemplo, no puede hacer lo siguiente:

Tiene que hacer esto:

Así:

lo sustituimos por un provider en el módulo de Dagger:

Esto funciona porque la clase MasterParser implementa Parser<Map<String, Integer>>:

annotatedWith(named()) es autoexplicativo. Anota el método con @Named para diferenciarlo de otros con el mismo tipo de retorno. Así este código con dos providers para un Parser<Map<String, Integer>>:

lo sustituimos en Dagger por un provider con la anotación @Named:

@Provides y @Named()

Parece ser que se puede usar @Provides como forma anotada de hacer un bind().toInstance() y @Named como forma anotada de annotatedWith(named()). Esto lo sustituimos por las mismas anotaciones pero del paquete javax.inject en el caso de @Provides. Así, este código se quedaría igual, pero asegúrate de que el paquete de las anotaciones es el correcto:

Resultado

Los módulos de Dagger resultantes serían (inicialmente) así:

Solo son así inicialmente porque ahora revisaremos todas las clases y tendremos que añadir más cosas.

2. Revisar todas las clases

Ahora viene una parte más larga y tediosa que la anterior. Tenemos que ir abriendo una por una todas las clases de todos los paquetes de nuestra aplicación e ir realizando los siguientes cambios.

2.1. Mover la anotación singleton

En RoboGuice la anotación @Singleton es del paquete com.google.inject y se antepone al nombre de la clase en su declaración. Si encontramos alguna, debemos moverla al módulo. Así este código:

lo sustituiríamos por:

Y asegúrate de que la anotación @Singleton que usas es la del paquete javax.inject.

2.2. Cambiar la anotación @Inject

Por fortuna, esta anotación marca los campos inyectados igual que en Dagger.

Lo único que tenemos que hacer en cada clase que la encontremos es:

  • Eliminar la importación com.google.inject.@Inject y cambiarla por la de java.inject.@Inject.
  • Añadir al componente un método inject() para inicializar la inyección en esa clase y llamarlo donde corresponda.

Así, esta inyección:

la convertiríamos en:

Si fuera una clase sin ciclo de vida, probablemente encontremos una llamada a RoboGuice.getInjector(). Este inyector lo sustituimos por nuestra llamada al componente. Así este código:

pasa a ser este otro:

2.3. De Provider a Provider

En RoboGuice hay un objeto Provider. Su función es la de crear una instancia nueva cada vez que se solicite. Se utiliza, al igual que en Dagger, cuando en una misma clase vas a necesitar una cantidad indeterminada de inyecciones del mismo tipo. Lo único que tenemos que hacer en cada clase en la que la encontremos es sustituir la importación de com.google.inject.Provider por la de javax.inject.Provider. Y cuidado, tampoco la confundas con la anotación @Provides.

Y si no nos gusta esta opción, podemos sustituirlo por una inyección puntual exponiendo el provider en el componente. Así este código:

se convertiría en este:

2.4. De RoboClases a Clases

Encontraremos que RoboGuice obliga al uso de RoboActivity, RoboFragment y un montón de clases más que comienzan por “Robo”. Pues todo lo que sea un Robo lo cambiamos por la clase normal:

  • RoboActivity -> AppCompatActivity
  • RoboFragment -> Fragment (support-v4)
  • RoboFragmentActivity -> AppCompatActivity
  • RoboDialogFragment -> DialogFragment
  • RoboService -> Service
  • RoboBroadcastReceiver -> BroadcastReceiver
  • RoboSimpleAsyncTaskLoader -> AsyncTaskLoader

Es altamente aconsejable hacer que todas estas clases hereden de su correspondiente clase base (BaseFragment, BaseActivity…).

Además, para los RoboBroadcastReceiver, hay que cambiar el método handleReceive() por onReceive(). Así esta clase:

se convertiría en esta otra:

3. Crear el DIManager

Si te fijas, para acceder al componente hemos estado utilizando una clase llamada DIManager. Como hay una librería y una aplicación, cada una tiene su propio componente (el por qué se explica en la parte 2) y cada componente tendrá un DIManager, que sirva para inicializarlo y acceder a él:

Como ves, tendrás que inicializar el DIManagerLibrary llamando al método init() para poder pasarle un contexto. Esto lo harás en la clase aplicación:

4. Eliminar el resto de basurilla

Y ahora eliminaremos las dependencias de RoboGuice de la aplicación. Ve al build.gradle tanto de la aplicación como de la librería y elimina todo lo que haya de RoboGuice.

También ve al AndroidManifest.xml de la aplicación y busca elementos <meta%gt; dentro del elemento application que tengan esta apariencia:

Esto sirve a RoboGuice para indicar cuáles son los módulos. ¡Fuera todo eso!

5. Arrancar – Ensayo y error

Y ahora llega el momento cumbre. “¡Trata de arrancarlo, Carlos!”

Esta es la fase de ensayo y error. Intentarás compilar y comenzarás a recibir fallos. Los más comunes que verás:

  • Nos hemos dejado alguna anotación del paquete com.google.inject por ahí perdida
  • Nos faltan dependencias para construir el grafo o tenemos dependencias duplicadas
  • Se detectan dependencias cíclicas (se solucionarán en la parte 3 de esta serie de artículos)

Una vez hayas conseguido arrancar, ya solo tienes que preocuparte por la sobrescritura de módulos. Como habrás podido ver, tanto el AppDaggerModule como elLibraryDaggerModule tienen un provider de TagManager y, si recuerdas la inicialización de RoboGuice, tenemos que hacer que la instancia sea la misma tanto cuando la pide la librería internamente como cuando la pide la aplicación (Modules.override(new LibraryModule()).with(new AppModule()))); Avanza a la segunda parte de esta serie de artículos.

 

0 Comentarios

Contesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

©2018 Codictados Comunidad libre para el aprendizaje de codigo Online

o

Inicia Sesión con tu Usuario y Contraseña

o    

¿Olvidó sus datos?

o

Create Account