Inyección de Dependencias usando Hilt
La Inyección de Dependencias (DI) es fundamental para construir aplicaciones Android robustas, escalables y fáciles de testear. Mientras que existen varias librerías para lograrlo, Hilt se ha establecido como la solución recomendada y estándar de Google. Construido sobre la potencia de Dagger, Hilt simplifica enormemente la implementación de DI en Android.
Si buscas una forma estandarizada, con menos boilerplate que Dagger puro y con excelente integración con los componentes de Android Jetpack, Hilt es la respuesta. ¡Descubramos cómo funciona!
¿Por qué elegir Hilt?
Hilt ofrece ventajas significativas para el desarrollo Android:
- Estándar de Android: Es la librería DI recomendada por Google, lo que asegura buena documentación, soporte y alineación con las prácticas modernas de Android.
- Menos Boilerplate (vs. Dagger): Reduce drásticamente el código de configuración necesario en comparación con usar Dagger directamente en Android.
- Seguridad en Tiempo de Compilación: Al igual que Dagger, Hilt valida las dependencias en tiempo de compilación, detectando errores antes de ejecutar la app.
- Integración con Jetpack: Proporciona extensiones directas para inyectar fácilmente componentes como ViewModels, WorkManager, Navigation y más.
- Ciclos de Vida y Scoping Simplificados: Define componentes y scopes estándar ligados al ciclo de vida de Android (
@Singleton
,@ActivityScoped
,@ViewModelScoped
, etc.), facilitando la gestión del tiempo de vida de las dependencias. - Basado en Dagger: Aprovecha la eficiencia y el rendimiento comprobados de Dagger bajo el capó.
Configurando Hilt en tu Proyecto Android
Integrar Hilt requiere unos pocos pasos de configuración inicial:
Paso 1: Añadir Dependencias y Plugin de Gradle
Necesitas configurar el plugin de Hilt y añadir las dependencias necesarias. Usaremos KSP (Kotlin Symbol Processing) que es más rápido que KAPT, aunque KAPT también funciona.
1. Archivo build.gradle.kts
(Nivel de Proyecto):
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.X.X" apply false // Usa tu versión
id("org.jetbrains.kotlin.android") version "1.9.XX" apply false // Usa tu versión
// Plugin de Hilt
id("com.google.dagger.hilt.android") version "2.51.1" apply false // Usa la última versión estable de Hilt
// Plugin KSP (Recomendado)
id("com.google.devtools.ksp") version "1.9.XX-1.0.XX" apply false // Usa la versión KSP compatible con tu Kotlin
}
2. Archivo build.gradle.kts
(Nivel de App):
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
// Aplicar plugin KSP (o kotlin-kapt si usas KAPT)
id("com.google.devtools.ksp")
// Aplicar plugin Hilt
id("com.google.dagger.hilt.android")
// Opcional: si usas kapt en lugar de ksp
// id("kotlin-kapt")
}
android {
// ... otras configuraciones (compileSdk, defaultConfig, etc.)
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 // O superior
targetCompatibility = JavaVersion.VERSION_1_8 // O superior
}
kotlinOptions {
jvmTarget = "1.8" // O superior
}
}
dependencies {
// Dependencias de Hilt
implementation("com.google.dagger:hilt-android:2.51.1") // Usa la misma versión que el plugin
ksp("com.google.dagger:hilt-compiler:2.51.1") // Para KSP
// Si usas KAPT en lugar de KSP:
// kapt("com.google.dagger:hilt-compiler:2.51.1")
// (Opcional) Para integración con ViewModels en Jetpack Compose Navigation
implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Usa la última versión
// Otras dependencias de tu app (androidx.core, appcompat, material, compose, etc.)
}
// Opcional: si usas kapt
// kapt {
// correctErrorTypes = true
// }
Nota: Reemplaza las versiones con las últimas estables disponibles. Sincroniza tu proyecto Gradle después de los cambios.
Paso 2: Anotar la Clase Application
Hilt necesita una clase Application anotada con @HiltAndroidApp
para iniciar la generación de código.
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApp : Application() {
// Puedes dejarla vacía o añadir lógica de inicialización adicional si es necesario
override fun onCreate() {
super.onCreate()
// ...
}
}
No olvides declarar esta clase en tu AndroidManifest.xml
:
<manifest ...>
<application
android:name=".MyApp"
... >
<!-- otras configuraciones -->
</application>
</manifest>
Paso 3: Anotar Componentes Android y Definir Dependencias
Para que Hilt pueda inyectar dependencias en tus Activities, Fragments, Services, etc., debes anotarlos con @AndroidEntryPoint
.
Para definir cómo crear las dependencias, Hilt usa principalmente:
@Inject constructor
: La forma preferida. Anota el constructor de tu clase. Hilt sabrá cómo crearla siempre que también sepa crear sus parámetros.- Módulos Hilt (
@Module
y@InstallIn
): Para casos donde la inyección por constructor no es posible (interfaces, clases externas, configuración compleja).@Binds
: Dentro de un módulo, para indicar a Hilt qué implementación usar para una interfaz (más eficiente que@Provides
).@Provides
: Dentro de un módulo, para indicar cómo construir instancias de tipos que no controlas (ej. de librerías externas como Retrofit, Room) o cuando necesitas lógica de construcción.@InstallIn(Component::class)
: Indica en qué componente de Hilt (y por tanto, con qué scope) se instalará el módulo (ej:SingletonComponent::class
,ActivityComponent::class
,ViewModelComponent::class
).
Ejemplo de Módulo:
package com.example.myapp.di
import com.example.myapp.data.UserRepository
import com.example.myapp.data.UserRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class) // Disponible durante toda la vida de la app
abstract class AppModule {
// Usamos @Binds para decirle a Hilt que cuando se pida un UserRepository,
// debe proveer una instancia de UserRepositoryImpl.
@Binds
@Singleton // La instancia será singleton
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
// Si necesitaras @Provides (ejemplo):
// @Provides
// @Singleton
// fun provideApiService(): ApiService {
// return Retrofit.Builder().baseUrl("...").build().create(ApiService::class.java)
// }
}
// --- Clases de ejemplo ---
package com.example.myapp.data
import javax.inject.Inject
import javax.inject.Singleton
interface UserRepository {
suspend fun getUser(id: String): String
}
// Hilt sabe cómo crear UserRepositoryImpl porque usamos @Inject en su constructor.
// @Singleton aquí no es estrictamente necesario si ya lo definimos en @Binds, pero puede ser útil.
@Singleton
class UserRepositoryImpl @Inject constructor() : UserRepository {
override suspend fun getUser(id: String): String {
kotlinx.coroutines.delay(500) // Simular trabajo
return "Hilt: Datos del Usuario $id"
}
}
Inyectando Dependencias con Hilt
En Activities, Fragments, Services (Anotados con @AndroidEntryPoint
):
Usa la anotación @Inject
en campos (normalmente lateinit var
). Hilt los inicializará por ti.
Para ViewModels, usa el delegado estándar by viewModels()
; Hilt se integra automáticamente.
package com.example.myapp.ui
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels // Importar delegado estándar
import com.example.myapp.data.UserRepository // Podrías inyectarlo aquí, pero es mejor via ViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint // ¡Esencial para que Hilt inyecte aquí!
class MainActivity : AppCompatActivity() {
// Inyectar ViewModel (Hilt maneja la factoría automáticamente)
private val myViewModel: MyViewModel by viewModels()
// También puedes inyectar otros tipos definidos en Hilt directamente
// @Inject lateinit var userRepository: UserRepository // Ejemplo, aunque usualmente se accede via ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(...)
// Usar el ViewModel
myViewModel.loadUserData()
// Observar datos del ViewModel
// myViewModel.userDataFlow.observe...
}
}
En ViewModels:
Anota la clase ViewModel con @HiltViewModel
y usa @Inject constructor
para recibir sus dependencias.
package com.example.myapp.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapp.data.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel // Anotación clave
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject // Para el constructor
@HiltViewModel // Indica a Hilt cómo proveer este ViewModel
class MyViewModel @Inject constructor( // Hilt inyectará UserRepository aquí
private val userRepository: UserRepository
) : ViewModel() {
private val _userDataFlow = MutableStateFlow("Hilt: Inicial...")
val userDataFlow: StateFlow = _userDataFlow
fun loadUserData() {
viewModelScope.launch {
try {
_userDataFlow.value = "Hilt: Cargando..."
val user = userRepository.getUser("hiltUser")
_userDataFlow.value = user
} catch (e: Exception) {
_userDataFlow.value = "Hilt: Error - ${e.message}"
}
}
}
}
En Jetpack Compose:
La forma principal de obtener dependencias en Composables es a través de un ViewModel inyectado con Hilt.
Usa la función Composable hiltViewModel()
que proviene de la dependencia androidx.hilt:hilt-navigation-compose
(si necesitas que el ViewModel esté asociado a un destino de Navigation Compose) o simplemente del core de Hilt para ViewModels asociados a la Activity/Fragment anfitrión.
package com.example.myapp.ui.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
// Import para hiltViewModel
import androidx.hilt.navigation.compose.hiltViewModel
import com.example.myapp.ui.MyViewModel // Tu ViewModel anotado con @HiltViewModel
@Composable
fun UserProfileScreen(
// Inyecta el ViewModel usando la función Composable de Hilt
// Hilt proveerá la instancia correcta asociada al scope adecuado (ej. Navigation graph)
myViewModel: MyViewModel = hiltViewModel()
) {
// Observa el StateFlow del ViewModel
val userData by myViewModel.userDataFlow.collectAsStateWithLifecycle()
// Llama a la función del ViewModel
LaunchedEffect(key1 = Unit) {
myViewModel.loadUserData()
}
Column {
Text(text = "Perfil de Usuario (Compose con Hilt)")
Text(text = userData)
Text(text = "ViewModel instance hash: ${myViewModel.hashCode()}")
}
}
// --- Para usar este Composable ---
// Asegúrate de que la Activity/Fragment que hostea Compose esté anotada con @AndroidEntryPoint.
// Luego, llama a UserProfileScreen() desde setContent o tu grafo de Navigation Compose.
// Ejemplo en una Activity:
/*
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.example.myapp.ui.compose.UserProfileScreen
import com.example.myapp.ui.theme.MyApplicationTheme
import dagger.hilt.android.AndroidEntryPoint // ¡Activity debe ser EntryPoint!
@AndroidEntryPoint
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
UserProfileScreen()
}
}
}
}
*/
Nota: Inyectar dependencias directamente en funciones Composable (que no sean ViewModels) es posible pero menos común y requiere que el Composable se llame desde un @AndroidEntryPoint
. La práctica recomendada es pasar dependencias a través del ViewModel.
Conclusión
Hilt simplifica enormemente la inyección de dependencias en Android al estandarizar la configuración y reducir el boilerplate asociado con Dagger. Su integración nativa con los componentes de Android y Jetpack, junto con la seguridad de la validación en tiempo de compilación, lo convierten en una opción robusta y eficiente para cualquier proyecto Android moderno.
Adoptar Hilt significa seguir las mejores prácticas recomendadas por Google y obtener una base sólida para construir aplicaciones mantenibles y testeables.
Para explorar más a fondo, consulta la documentación oficial de Hilt.
Comentarios
Publicar un comentario