Prácticamente todo el mundo que quiere crear código en Android de forma desacoplada y fácil de testear, recurre tarde o temprano a Dagger para ello.
Aunque hay alguna cosa que funciona un poco diferente a la hora de configurar Dagger en Kotlin, la mayor parte es bastante sencilla, y en pocos pasos te la voy a mostrar hoy aquí.
También decirte que, gracias a la potencia de Kotlin, hay otras maneras de solventar la inyección, e incluso algunas librerías hechas exclusivamente en Kotlin para ello.
Pero Dagger sigue siendo una opción perfectamente válida, y una de las más versátiles (si no la que más).
Si quieres aprender cuál es la situación de Kotlin en el mercado, por qué debes aprenderlo y los primeros pasos para que veas lo fácil que es, he preparado un training gratuito de hora y media al que puedes unirte haciendo click aquí
Disclaimer: En este artículo no voy a explicar cómo se usa Dagger 2, esto ya se da por sabido. Si tienes alguna duda, busca alguno de los muchos artículos que puedes encontrar por Internet.
Configurando el proyecto para que utilice Dagger 2
Si ya tienes configurado el plugin de Kotlin en tu proyecto, lo único que necesitas es configurar kapt
.
Si ya utilizabas Dagger, seguramente conoces apt
. kapt
no es más que la versión para Kotlin, que crea las clases autogeneradas necesarias para Dagger.
Para configurarlo, necesitas añadir lo siguiente al build.gradle
:
kapt { generateStubs = true }
Puedes añadirlo justo antes de la sección de dependencies. En algún sitio leí (no recuerdo dónde), que en un tiempo esta parte tampoco será necesaria.
Ahora ya solo necesitas añadir las dependencias del compilador de Dagger (usando kapt
para que no se incluya en el apk) y de la librería propiamente dicha:
kapt 'com.google.dagger:dagger-compiler:2.5' compile 'com.google.dagger:dagger:2.5'
Ya está todo listo para empezar a utilizar Dagger.
Implementación del módulo principal
Como bien sabes, para el grafo principal vas a necesitar un Module
y un Component
.
El módulo de aplicación, en este caso sencillo, sólo devolverá la instancia de la aplicación propiamente dicha.
Para ello crearemos un clase anotada con @Module
, que recibirá por constructor la instancia de aplicación, la almacenará en una property, y la devolvería mediante un método anotado con @Provides @Singleton
:
@Module class AppModule(val app: App) { @Provides @Singleton fun provideApp() = app }
Ya ves que, incluso para esta clase tan sencilla, el código es bastante más simple que en Java.
Ahora nos queda implementar el Component
, que necesita un array de módulos a cargar, y especificar quién lo va a poder inyectar manualmente:
@Singleton @Component(modules = arrayOf(AppModule::class)) interface AppComponent { fun inject(app: App) }
Ahora ya solo queda crear la clase App
, que se encargará de generar el grafo:
class App : Application() { val component: AppComponent by lazy { DaggerAppComponent .builder() .appModule(AppModule(this)) .build() } override fun onCreate() { super.onCreate() component.inject(this) } }
Lo interesante a ver aquí es que, gracias a la declaración lazy
, podemos especificar el valor del grafo en la definición de la propiedad, y además conseguir así un acceso de solo lectura a dicha propiedad.
el código definido por la propiedad no se ejecutará hasta que no se haga component.inject(this)
, de tal forma que para ese momento this
ya existe y se puede crear el grafo de forma segura.
Implementación de un módulo por ámbito
Los módulos por ámbito permiten que esa parte del grafo solo viva durante el tiempo de vida del objeto que lo crea.
De esta forma, podemos crear subgrafos que vivan y mueran con una Activity, por ejemplo.
Nos crearíamos nuestro módulo con lo que necesitemos:
@Module class HomeModule(val activity: HomeActivity) { }
Un Subcomponent
de forma muy parecida al anterior, indicando que se va a inyectar en la HomeActivity
:
@Singleton @Subcomponent(modules = arrayOf(HomeModule::class)) interface HomeComponent { fun inject(activity: HomeActivity) }
Y un método plus
en AppComponent
, para indicar que a ese componente se le pueden añadir subcomponentes de ese tipo:
interface AppComponent { ... fun plus(homeModule: HomeModule): HomeComponent }
Ahora, en la HomeActivity
ya solo necesitas declarar el subcomponente:
val component by lazy { app.component.plus(HomeModule(this)) }
Y ya lo puedes inyectar tras el setContentView
:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) component.inject(this) }
Si te preguntas de dónde viene app
, se trata de una propiedad de extensión escrita tal que así:
val Activity.app: App get() = application as App
Es simplemente una forma de evitar tener que hacer casting cada vez que accedes a application
si tienes una clase propia.
Conclusión
Así de sencillo es utilizar Dagger 2 en Kotlin. Ya no tienes excusa para no hacer tu código más desacoplado también en Kotlin.
Si todo esto te apasiona tanto como a mí, te animo a que te apuntes a mi training gratuito donde te contaré todo lo que necesitas para aprender a crear tus Apps Android en Kotlin desde cero.
¿De dónde sale la referencia app en esta línea de código?
val component by lazy { app.component.plus(HomeModule(this)) }
Supongo que será de App, pero no la encuentra. ¿Hay que hacer añadir algún código adicional para obtener una referencia a ‘app’?
Es una propiedad de extensión para hacer casting de Application. He añadido el código al ejemplo. Gracias!
Hay alguna manera de utilizar multibindings con Dagger2 y Kotlin?
Tengo este provides pero dagger no me lo detecta. En cambio en java no tengo problema.
@Singleton
@Provides
@IntoSet
fun providesQueries(getTokenByCredentials: GetTokenByCredentialsApi): Query{
return getTokenByCredentials
}