De RoboGuice a Dagger 2
Cómo migrar de un a librería a la otra teniendo en cuenta la independencia de las librerías, la sobrecarga de módulos y las dependencias cíclicas. Esta es la introducción del tutorial de migración de RoboGuice a Dagger 2. Aquí tienes el enlace a cada una de las partes que lo componen:- Parte 1: Conversión de RoboGuice a Dagger 2
- Parte 2: Sobrecarga de módulos
- Parte 3: Dependencias cíclicas
- Parte 4: Extras
Introducción
Trabajando para mi empresa, recibí un proyecto externo que utilizaba la librería RoboGuice para la inyección de dependencias. El proyecto consiste en una aplicación con una librerías. Las inyección de dependencias estaba configuradas dentro de ambas con RoboGuice. Mi misión: eliminar RoboGuice, que ha quedado obsoleta desde hace ya dos años, y migrar a Dagger 2.Las grandes diferencias entre RoboGuice y Dagger
En Dagger 1 teníamos elObjectGraph
, que en Dagger 2 se convierte en la clase Component
, con el fin de poder dividir el ObjectGraph
en varios componentes y hacer uso de los ámbitos (scopes). Para inyectar algo necesitamos tener acceso a una instancia de esta clase Component
en cualquier lugar en el que queramos inicializar la inyección.
En RoboGuice, por el contrario, estamos abstraídos del grafo de dependencias. Uno solo tiene que hacer que sus clases con ciclo de vida hereden de unas clases homónimas de RoboGuice (Activity
-> RoboActivity
, Fragment
-> RoboFragment
…) y utilizar un inyector (Injector
) en las clases que no tienen ciclo de vida, y él solo se encarga de inicializar la inyección.
De esta diferencia en el funcionamiento surgen los siguientes problemas:
- Pérdida de la independencia de las librerías. Es una mala idea utilizar Dagger en librerías. No lo hagáis siempre que lo podáis evitar.
- Sobrescritura de módulos. En Dagger es complicada y tiene sus limitaciones.
- Dependencias cíclicas. En RoboGuice puede haber dependencias cíclicas pero en Dagger, desgraciadamente, es imposible.
Pérdida de la independencia de las librerías
Mientras que en RoboGuice la inyección funciona de manera automática en las clases con ciclo de vida y obteniendo una instancia de la claseInjector
en el resto, en Dagger ambos tipos de clases necesitan un «injector» como el de RoboGuice y este no pertenece a la librería de Dagger, sino que hay que crearlo a mano y hacer que esté disponible en cualquier lugar en el que tengamos que inicializar las dependencias, lo que hace necesario que, o bien tanto la librería como aplicación tengan el mismo componente, o bien cada una gestione el suyo propio.
El primer caso es una vergüenza en términos de desacoplamiento, ya que la librería se vería obligada a conocer el componente de la aplicación que la va a utilizar y nunca podría ser independiente ni reutilizables. En el segundo, el problema vendría con el patrón singleton. Dos componentes distintos (o incluso dos instancias de un mismo componente) crean dos instancias que son únicas pero distintas entre sí. Por lo que no podemos tener independencia de librería e instancias realmente únicas en el .apk final.
Sobrecarga de módulos
Parece ser que en RoboGuice podemos sobrescribir todos los providers de un módulo con los providers que se declaren en otro. Mira la orden con la que se inicializa RoboGuice:
1 2 3 4 5 6 7 8 9 10 11 |
public class MiAplicacion { @Override public void onCreate() { super.onCreate(); RoboGuice.setBaseApplicationInjector(this, RoboGuice.DEFAULT_STAGE, RoboGuice.newDefaultRoboModule(this), Modules.override(new LibraryModule()).with(new AppModule())); } } |
1 |
Modules.override(new LibraryModule()).with(new AppModule())); |
String
con el valor «lib» y la aplicación permite inyectar un String
con el valor «app», RoboGuice hace que siempre se inyecte el valor «app», tanto en las clases que gestione la librería internamente como en las de la aplicación. Dagger, por el contrario, con un solo componente, sí que devolvería el valor «app» para la librería y la aplicación pero nos cargaríamos la independencia de la librería. Y sin embargo con dos componentes devolvería «app» para la aplicación y «lib» para la librería, por lo que nos cargamos el patrón singleton.
Dependencias cíclicas
RoboGuice permite que una clase A tenga inyectada una clase B y que B también tenga inyectada a A. Esto en Dagger no se puede hacer, ya que, en la inicialización de A se inicializaría B, la cual necesitaría inicializar A, que volvería a requerir la inicialización de B, y así hasta que obtuviéramos un error por falta de memoria.Aclaraciones previas
Vamos a aclarar algunos términos de Dagger para que entendamos qué hacen: El módulo Es un conjunto de métodos provider. Sirve para indicar qué objetos se pueden inyectar. La propiedad includes sirve para que el módulo se agencie todos los métodos de los módulos que incluya. El componente- modules: para resolver dependencias, el componente utiliza los providers de los módulos indicados.
- dependencies: para resolver dependencias, el componente utiliza los providers expuestos en los componentes indicados. Solo los expuestos. Pero para hacer esto, el módulo que añadas como dependencia no puede tener ámbito.
@Inject
, se crea un componente, en el cual no puede haber providers duplicados. Si tenemos cinco módulos añadidos a un componente, entre los cinco no puede haber más de un provider para un mismo tipo objeto.
Cuando llamas a getComponent.inject()
, lo que haces es inicializar todos los campos marcados con @Inject
de la clase en la que lo llamas. En el componente tiene que haber necesariamente un provider (en sus módulos o expuesto en alguno de los componentes de los que depende) para satisfacer cada campo inyectado.