Con la llegada de Android 11 también han llegado algunas novedades como Dagger Hilt, una librería de inyección de dependencias que ahora se convierte en la opción recomendada por Google.

Dagger Hilt aún es una alpha, así que hoy te voy a contar qué es esto de la inyección de dependencias, y mis primeras impresiones sobre Dagger Hilt comparándolo con otras alternativas.

Dagger Hilt ¿Por qué ahora?

La comunidad Android lleva bastante tiempo un poco perdida sobre cómo implementar la inyección de dependencias.

Dagger ha sido siempre la alternativa elegida por la gran mayoría, pero es cierto que es una librería muy compleja y que la curva de aprendizaje es alta.

Además, existen muchas maneras de hacer lo mismo, y siempre ha habido muchas dudas sobre cuál era la mejor.

Es por ello que la comunidad Android lleva desde tiempo pidiendo a Google que se posicionara y nos diera una solución apta e integrada en el framework de Android.

Y esto es lo que tenemos con Hilt.

¿Pero por qué necesito Dagger Hilt en primer lugar?

Para eso nos tenemos que remontar a qué es un inyector de dependencias y qué problema soluciona.

Un inyector de dependencias es una entidad en tu código que provee las dependencias que otras entidades necesitan de forma activa.

Pero seguramente esto no te diga nada, así que vamos empezar por el principio.

El problema de las dependencias

Normalmente, cuando empiezas a preocuparte por la calidad de tu software, necesitas empezar a crear entidades y módulos que solamente hagan una cosa.

Este se conoce como el Principio de Responsabilidad Única de los Principios SOLID, y es de los principios más importantes en la programación: si una entidad solo hace una cosa, solo tendrá una razón para cambiar, y por tanto los efectos de que algo externo lo modifique se reducen.

Una unidad independiente es mucho más fácil de modificar, reemplazar y testear

Si quieres saber más sobre los Principios SOLID, puedes descargarte esta guía gratuita que he creado para ti.

Al hacer esto, tu módulo empezará a depender de muchos otros, y si los creamos dentro del propio módulo tendremos dos problemas principales

  1. Desde fuera del mismo, no somos capaces de ver con qué otros módulos interactúa. Tendríamos que entrar en el código fuente y estudiarlo a fondo, lo que es una pérdida de tiempo
  2. Durante los tests, no tendremos forma fácil de probar nuestro módulo de forma aislada, ya que al probar cualquiera de sus funcionalidades irremediablemente estaremos ejecutando parte de la funcionalidad de otro módulo

Así que la solución pasa por lo siguiente:

La Inversión de Control al rescate

Si en vez de que nuestro módulo o entidad cree sus propias instancias, se las proveemos nosotros mediante constructor, esto nos ayudará a solucionar ambos problemas:

  1. Las dependencias quedan explícitas en el constructor, así que no hay dudas de cuáles son los módulos de los que depende el nuestro.
  2. En los tests podemos proveer módulos alternativos que hagan que podamos probar nuestro componente de forma aislada.

Para que esto segundo sea cierto, podemos utilizar librerías como Mockito, o proveer nuestros propios dobles de test.

Esto segundo solo será posible si, además de la inversión de control, aplicamos la Inversión de Dependencias (otro Principio SOLID).

No los confundas, porque son dos conceptos muy similares: la Inversión de Dependencias nos dice que deberíamos depender de abstracciones, no concreciones. Es decir, que uses interfaces en vez de clases concretas.

La provisión de dependencias se vuelve una tarea compleja

Imagina que tienes en tu App un montón de módulos, y que todos ellos exponen sus dependencias y esperan que alguien se las provea.

Esto se vuelve de una complejidad enorme, y es importante hacerlo de forma estructurada y sencilla.

Hay muchos sistemas de provisión de dependencias, y hoy no voy a entrar en ellos (coméntame en los comentarios si quieres que otro día ahondemos), pero hoy vamos a centrarnos en la inyección de dependencias.

Dagger Hilt y la inyección de dependencias

Ya hemos entendido el problema, y que la solución es la inyección de dependencias. ¿Pero qué es la inyección de dependencias?

Muy a grandes rasgos es una forma automática de proveer las dependencias a un módulo.

Imagínate esto como un saco de piezas que conforman tu App. Cuando queremos crear una pieza nueva, el inyector se irá al saco, buscará las piezas que necesita, y las usará para crear esa nueva pieza, que a su vez se incluye también en el saco por si alguien más la necesita.

El objetivo de Dagger Hilt es hacer esto muy sencillo e integrado con el framework de Android.

Cómo usar Dagger Hilt

Vamos a ver los pasos de cómo se usaría Dagger Hilt en un proyecto que ahora mismo usa Dagger.

Para ello, mis cambios los haré a partir de esta etiqueta en mi repositorio de Architect Coders, que es la base de código que sustenta este programa de formación.

Configura el build.gradle de app

Se requieren las siguientes dependencias:

dependencies {
    implementation 'com.google.dagger:hilt-android:2.28-alpha'
    kapt 'com.google.dagger:hilt-android-compiler:2.28-alpha'
}

Además, necesitas configurar el kapt para que corrija los errores de tipos:

kapt {
    correctErrorTypes = true
}

Añade el plugin de Gradle de Hilt

Este es un paso opcional, pero que simplifica la forma de usar Hilt en Android, así que te recomiendo que lo hagas. En el build.gradle principal, añade el plugin de Hilt:

dependencies {
    ...
    classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}

Identifica cuál va a ser el Application

Para ello, lo único que tienes que hacer es usar la anotación @HiltAndroidApp:

@HiltAndroidApp
class MoviesApp : Application()

Si has usado Dagger anteriormente, recordarás que aquí se inicializaba en onCreate(). Con Hilt no hace falta, todo esto lo hace solo.

Crea módulos y añadelos a su Component correspondiente

Nuevamente, si has usado Dagger, recordarás que tienes módulos para los que luego tienes que crear un Component, y asignar el módulo al component correspondiente.

En Hilt se simplifica esto porque han incluido una serie de componentes con ciertos superpoderes especiales.

Si por ejemplo queremos tener dependencias a nivel de aplicación, usaríamos el ApplicationComponent. Solo tenemos que declararlo con la anotación @InstallIn:

@Module
@InstallIn(ApplicationComponent::class)
class AppModule

Luego ya solo tendríamos que definir las dependencias que queremos proveer con este módulo mediante la anotación @Provides. Si son dependencias que queremos que sean únicas para la App (en vez de que se genere una nueva cada vez que se pida), utilizaremos la anotación @Singleton:

    @Provides
    @Singleton
    fun databaseProvider(app: Application): MovieDatabase = Room.databaseBuilder(
        app,
        MovieDatabase::class.java,
        "movie-db"
    ).build()

Como ves, esta dependencia a su vez necesita el Application. ¿Y de dónde sale ese Application? De ahí los superpoderes.

Cada Component tiene una serie de dependencias a las que se puede acceder sin necesidad de hacer nada. Estos son cada uno de los componentes y sus dependencias:

ComponentInyecta
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponentView with @WithFragmentBindings
ServiceComponentService
Extraída de la referencia de Dagger Hilt

Definiendo dependencias a nivel de Activity

Hay veces que solo queremos que ciertas dependencias estén activas durante el tiempo de vida de un elemento del framework de Android. Como ves en el recuadro anterior, se pueden definir componentes para distintos elementos, pero aquí lo vamos a ver con una Activity.

Para ello, solo tenemos que usar el ActivityComponent:

@Module
@InstallIn(ActivityComponent::class)
class MainActivityModule {

    @Provides
    fun mainViewModelProvider(getPopularMovies: GetPopularMovies) = MainViewModel(getPopularMovies)

    @Provides
    fun getPopularMoviesProvider(moviesRepository: MoviesRepository) =
        GetPopularMovies(moviesRepository)
}

Declarar qué elementos pueden inyectar dependencias

Por defecto no podremos usar el módulo anterior en una Activity si no lo indicamos. Para ello, tendremos que usar la anotación @AndroidEntryPoint.

Hay una serie de elementos que se pueden marcar con esta anotación, que son:

  1. Activity
  2. Fragment
  3. View
  4. Service
  5. BroadcastReceiver

Para hacerlo con nuestra Activity, solo tendríamos que hacer:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var getPopularMovies: GetPopularMovies

    ...
}

Es importante tener claro que todo el código de inyección ocurre en el onCreate() de la clase correspondiente, así que no hacer nunca nada con las dependencias antes de llamar a super.onCreate()

¿Y qué pasa con el ViewModel?

Dagger Hilt por defecto no soporta ViewModel, pero en las librerías de Jetpack sí que tenemos una extensión para utilizarlo.

Para ello, necesitas incluir estas dependencias:

    implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
    kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'

Y después marcar el constructor del ViewModel con la anotación @ViewModelInject:

class MainViewModel @ViewModelInject constructor(private val getPopularMovies: GetPopularMovies) :
    ViewModel() {
   ...
}

Con esto, ya podríamos acceder al ViewModel sin necesidad de crearnos una factory, tanto desde ViewModelProvider() como desde el delegado by viewModels de fragment-ktx o activity-ktx

private val viewModel: MainViewModel by viewModels()

También hay que cambiar el component al que se asigna el módulo de Dagger correspondiente, para que las dependencias sobrevivan durante todo el tiempo de vida del ViewModel, y no se recreen cuando haya un cambio de configuración (como una rotación de pantalla):

@Module
@InstallIn(ActivityRetainedComponent::class)
class MainActivityModule

Y con todo esto, ya tendrías el ejemplo funcionando.

Puedes ver el código en esta rama del repositorio de Architect Coders.

Dagger Hilt es un claro paso adelante en simplicidad

Hilt mejora muchas de las complejidades que suponía empezar a trabajar con inyección de dependencias en Android, y hace este concepto mucho más accessible.

Por otro lado, sigue estando lejos de la simplicidad de otras soluciones como Koin, y por tanto la curva de aprendizaje sigue siendo un poco mayor.

Uno de los temas que me suele preocupar cuando uso librerías atadas al framework de Android es que no se puedan usar en módulos de Java/Kotlin, y por tanto haga depender todos mis módulos del framework de Android.

Según he hablado con Manuel Vivo, este no es el caso de Hilt, pero esto lo vamos a dejar para otro artículo. Si te interesa saber cómo hacer una App multimódulo con Hilt, déjamelo en los comentarios y lo podemos ver en otro momento.

En general Dagger Hilt me gusta mucho, es un gran paso adelante, pero habrá que ver cómo se comporta ante Apps grandes y más complejas, sobre todo por las restricciones que impone en cuanto a componentes. También si la comunidad lo adopta y deja de lado otras opciones.

Solo el tiempo lo dirá. En cualquier caso aún es una alpha, así que trátese con cuidado.

Author: Antonio Leiva

Soy un apasionado de Kotlin. Hace ya más de dos años que estudio el lenguaje y su aplicación a Android para ayudarte a ti a aprenderlo de la forma más sencilla posible.