Guía Completa de Inyección de Dependencias con Hilt en Android

  • Hilt simplifica Dagger proporcionando contenedores automáticos alineados con el ciclo de vida de Android.
  • Permite desacoplar el código mediante la inyección de constructor, módulos con @Provides y @Binds.
  • Gestiona el alcance de las instancias mediante componentes específicos como SingletonComponent o ActivityComponent.
  • Facilita el desarrollo de arquitecturas escalables y testeables, especialmente en entornos con Jetpack Compose y MVVM.

Hilt Android

Si te dedicas al desarrollo de aplicaciones Android, seguramente te habrás topado con la necesidad de organizar mejor tus clases para que el proyecto no se convierta en un caos absoluto. Aquí es donde entra en juego la inyección de dependencias, un patrón de diseño que, aunque al principio pueda parecer magia negra, es básicamente la forma de darle a cada clase los objetos que necesita sin que la clase tenga que fabricarlos ella misma.

Hilt llega para rescatarnos de la complejidad de Dagger, ofreciendo una capa superior que estandariza la inserción de dependencias. Al integrarse con Jetpack, Hilt se encarga de gestionar los contenedores y los ciclos de vida de forma automática, permitiéndonos centrarnos en la lógica de negocio y no en pasar objetos de un lado a otro como si estuviéramos en una mudanza.

Configuración Inicial del Entorno

Para empezar a darle caña a Hilt, lo primero es dejar el terreno listo en Gradle. Es fundamental añadir el plugin de hilt-android-gradle-plugin en el archivo de nivel de proyecto. Después, en el archivo del módulo de la aplicación, debemos aplicar el plugin y añadir las dependencias correspondientes, asegurándonos de que el compilador (ya sea mediante kapt o KSP) esté correctamente configurado.

Un detalle que no se nos puede escapar es que, para que todo ruede sin errores, el proyecto debe estar configurado para usar Java 17 (o Java 8 en versiones más antiguas), ya que es un requisito indispensable para que Hilt y Jetpack Compose funcionen en armonía. Si notas que el programa no compila al activar el plugin, revisa que las versiones de Kotlin y KSP coincidan exactamente, ya que un pequeño desfase ahí puede romper todo el proceso de compilación.

El Corazón de Hilt: La Clase Application

Cualquier app que quiera beneficiarse de esta librería tiene que tener una clase Application personalizada. No basta con crearla, sino que debemos marcarla con la anotación @HiltAndroidApp. Esta línea es la que dispara la generación de código de Hilt y crea el contenedor raíz de la aplicación.

Context en Android
Artículo relacionado:
Guía Completa sobre el Context en Android: Qué es y Cómo Gestionarlo

Este componente superior es vital porque se vincula al ciclo de vida global de la app y sirve de base para que todos los demás componentes puedan acceder a las dependencias que se definan a este nivel. Por supuesto, no olvides registrar esta clase en el archivo AndroidManifest.xml mediante el atributo name, o Hilt pasará totalmente desapercibido.

Inyectando Dependencias en Componentes de Android

Una vez que la aplicación está preparada, podemos empezar a inyectar dependencias en actividades, fragmentos, views o servicios. Para lograrlo, utilizamos la anotación @AndroidEntryPoint. Esto le dice a Hilt que esa clase es un punto de entrada y que debe generar un componente específico para ella.

Cuando queremos obtener una dependencia concreta dentro de una actividad, usamos la inyección de campo mediante la anotación @Inject. Es muy importante recordar que estos campos no pueden ser privados; si intentas ponerles el modificador private, el compilador te dará un error sin contemplaciones. En el caso de Jetpack Compose, basta con anotar la Activity raíz y podremos acceder a los ViewModels directamente en las funciones componibles.

Estrategias para Definir Vinculaciones

Hilt necesita saber cómo fabricar los objetos que le pedimos. La forma más directa es la inyección de constructor, donde ponemos @Inject justo antes del constructor de la clase. Así, Hilt analiza los parámetros y busca cómo proporcionar cada una de esas dependencias.

Pero claro, hay casos donde no tenemos el control total de la clase, como cuando usamos librerías externas tipo Retrofit u OkHttpClient. Para esto existen los Módulos de Hilt. Un módulo es una clase anotada con @Module y debe llevar @InstallIn para indicar en qué componente debe vivir esa dependencia.

  • Uso de @Provides: Se utiliza cuando la clase es de terceros o requiere un patrón de construcción complejo. Aquí definimos una función que devuelve la instancia necesaria.
  • Uso de @Binds: Es la opción ideal cuando queremos vincular una interfaz con una implementación concreta. Se define como una función abstracta que le dice a Hilt: «cuando alguien pida esta interfaz, dale esta implementación».

Gestión de Alcances y Ciclos de Vida

Por defecto, Hilt crea una instancia nueva cada vez que se solicita una dependencia. Sin embargo, hay veces que necesitamos que un objeto sea el mismo durante toda la vida de la app o de una pantalla. Para eso usamos los alcances de los componentes.

Si marcamos una clase con @Singleton y la instalamos en el SingletonComponent, tendremos una única instancia global. Si preferimos que el objeto viva solo lo que dure la actividad, usamos @ActivityScoped dentro del ActivityComponent. Es fundamental no abusar de los alcances, ya que mantener objetos en memoria durante demasiado tiempo puede acabar afectando al rendimiento de la aplicación.

Casos Especiales y Calificadores

A veces necesitamos inyectar dos implementaciones distintas de una misma interfaz. Para no volver locos al compilador, utilizamos calificadores. Un calificador es básicamente una anotación propia que sirve para etiquetar una vinculación específica, permitiéndonos diferenciar, por ejemplo, entre un cliente HTTP para autenticación y otro para peticiones generales.

mvvm
Artículo relacionado:
MVVM: El patrón de arquitectura de software definitivo para apps modernas

Además, Hilt nos facilita la vida con calificadores predefinidos como @ApplicationContext y @ActivityContext, que nos permiten obtener el contexto de Android sin tener que configurar módulos manuales para ello. Por último, para aquellas clases que Hilt no soporta nativamente (como los ContentProviders), podemos recurrir a la anotación @EntryPoint, creando un puente manual para extraer dependencias del grafo de Hilt. Comparte la información y mçás usuarios conocerán del tema.


Add as preferred source