Saltar al contenido
K
Jetpack y arquitecturaPaso a paso40 min de lectura · 10 min de máquina Básico

Hilt: instalarlo y el primer módulo

Al terminar: Entender qué es inyectar una dependencia y por qué hace falta alguien que monte las piezas; instalar Hilt y KSP en Marcador y escribir el primer módulo.

Al terminar esta lección, Marcador tendrá Hilt instalado y funcionando: la Application y la MainActivity conectadas a él, RegistrarGol construida por él y un primer módulo que sabe entregar el repositorio de partidos. La app se verá exactamente igual que antes. Lo que cambia es que, en la lección siguiente, ningún ViewModel tendrá que ir a buscar sus piezas.

Importa porque una app tiene decenas de piezas que dependen unas de otras, y montarlas a mano acaba en un fichero que crece y que nadie revisa. Hilt las monta por ti al compilar y, si falta alguna, te lo dice antes de que la app llegue a un móvil. Temas: dependencias y «recibir en vez de buscar»; anotaciones y KSP; Hilt, @Inject, módulos, @Provides, @Binds y @Singleton. Instalas KSP y Hilt en Gradle, y haces un ejercicio con una cafetería.

Recibir en vez de buscar

Una dependencia es algo que una clase necesita para funcionar: un repositorio, por ejemplo. Hay dos maneras de conseguirla.

ManeraEjemploConsecuencia
Ir a buscarlaprivate val repositorio = Repositorios.partidosLa clase depende de una implementación concreta; no se puede probar con otra
Recibirlaclass ListaViewModel(private val repositorio: PartidosRepository)La clase pide la interfaz y no sabe cuál llega

Inyectar una dependencia es recibirla de fuera, normalmente por el constructor.

El ejemplo entero, con unas piezas inventadas (una pizzería y su horno, no son clases de Marcador). Puedes pegarlo en el Kotlin Playground para verlo funcionar:

ejemplo mínimo: recibir en vez de buscar (con comentarios)
interface Horno { fun hornear(): String }                       // lo que la pizzería necesita
class HornoDeLena : Horno { override fun hornear() = "pizza al horno de leña" }
 
class Pizzeria(private val horno: Horno) {                      // RECIBE el horno: no sabe cuál es
    fun servir() = "Sale una ${horno.hornear()}"
}
 
fun main() {
    val horno = HornoDeLena()                                   // alguien monta las piezas...
    val pizzeria = Pizzeria(horno)                              // ...y se las da
    println(pizzeria.servir())
}

La consola muestra:

Sale una pizza al horno de leña

Pizzeria funciona igual con un horno de leña, uno eléctrico o uno de mentira para una prueba, porque solo conoce la interfaz Horno. Pero alguien tiene que escribir las dos líneas de main, las que crean el horno y se lo dan a la pizzería. En una pizzería con tres piezas es poca cosa. En una app con una base de datos, una conexión a internet, repositorios, casos de uso y pantallas, esas líneas son decenas, cada una depende de otras, y basta olvidar una para que la app se cierre al abrirla. Esa es la tarea que te quita Hilt.

Qué es Hilt

Hilt es la librería de Google que monta las piezas de una app Android por ti. Su nombre completo es Dagger Hilt: Dagger es el motor de inyección de Google y Hilt es la capa que lo adapta a Android, por eso sus paquetes se llaman com.google.dagger. A partir de aquí, la guía dice solo «Hilt».

Hilt funciona leyendo anotaciones. Una anotación es una etiqueta con @ sobre una clase, una función o un parámetro (@Composable, @Preview y @Serializable ya las conoces de Primeros pasos con Compose y JSON e instalar kotlinx.serialization). Por sí sola no hace nada: la lee otra herramienta, y esa herramienta actúa. Aquí, la herramienta lee tus etiquetas mientras compila y escribe código nuevo con el montaje de piezas. Ese ayudante se llama KSP.

Recuadro · KSP

KSP (Kotlin Symbol Processing) genera código mientras compila. Un símbolo es cualquier cosa con nombre de tu código (clase, función, anotación); un procesador es un programa que KSP ejecuta: lee los símbolos que le interesan (en Hilt, lo que lleva @Inject, @Module o @HiltAndroidApp) y escribe ficheros nuevos de Kotlin o de Java que se compilan con el resto. Tú escribes las etiquetas; el procesador escribe el código aburrido.

Se parece al plugin de serialización pero no es lo mismo. Ese es un plugin de compilador: vive dentro del compilador y modifica las clases que ya tienes (lo instalaste en JSON e instalar kotlinx.serialization). KSP es una herramienta aparte: no toca tus clases, las lee y crea clases nuevas. Es lo habitual en librerías con anotaciones, y se instala como un plugin de Gradle más con una línea nueva en dependencies.

En la práctica: usa la versión 2.3.12 (funciona con el Kotlin 2.4.20 y el Android Gradle Plugin 9.4.1 de tu proyecto) y no hay que añadir ningún plugin extra. Si en un tutorial ves una versión con forma x-y, como 2.2.21-2.0.5, es el formato viejo y Gradle no la encuentra. Room, en Room: entidades y DAO, usará este mismo KSP.

Las piezas de Hilt

Cuatro nombres para empezar; el resto los vas conociendo en el ejemplo, con un comentario cada uno.

NombreQué es
GrafoEl mapa de quién necesita a quién (Pizzeria necesita un Horno). Hilt lo dibuja al compilar
ComponenteEl contenedor que Hilt escribe con ese mapa. Guarda las piezas y las entrega. SingletonComponent es el de toda la app: nace al abrirse el proceso y muere con él
MóduloUn fichero de recetas para lo que Hilt no puede construir solo (@Module, e @InstallIn dice en qué componente viven)
@Singleton«Una sola pieza de este tipo en todo el componente». Todos los que la pidan reciben la misma
Pizzeria ──necesita──► Horno ──► HornoDeLena      (se une con @Binds)
         └─necesita──► Random ──► Random()        (se entrega con @Provides)

La misma pizzería, ahora con Hilt. Random es una clase de Java para sacar números al azar: no es tuya y no puedes escribirle @Inject. Este ejemplo solo se lee: necesita Hilt y no corre en el Playground.

ejemplo mínimo: una pizzería con Hilt (con comentarios)
interface Horno { fun hornear(): String }
 
class HornoDeLena @Inject constructor() : Horno {        // @Inject constructor: Hilt sabe construirlo, sin recetas
    override fun hornear() = "pizza al horno de leña"
}
 
class Pizzeria @Inject constructor(                      // Hilt también sabe construir esta...
    private val horno: Horno,                            // ...y le pasa un Horno (busca quién lo da)
    private val azar: Random,                            // ...y un generador de azar
) {
    fun servir() = "Sale una ${horno.hornear()}"
    fun monedaAlAire() = if (azar.nextBoolean()) "cara" else "cruz"
}
 
@Module                                                  // este fichero guarda recetas para Hilt
@InstallIn(SingletonComponent::class)                    // ...que viven en el componente de toda la app
abstract class HornosModule {                            // abstract: la clase no se construye, solo declara @Binds
    @Binds                                               // «quien pida un Horno, recibe un HornoDeLena»
    abstract fun horno(impl: HornoDeLena): Horno         // abstract: sin cuerpo, Hilt escribe el resto
}
 
@Module
@InstallIn(SingletonComponent::class)
object AzarModule {                                      // object: tiene funciones con cuerpo (@Provides)
    @Provides                                            // la receta de compra de una clase que no es tuya
    fun azar(): Random = Random()
}

Hilt puede construir una Pizzeria y entregársela a quien se la pida; cuando alguien llame a servir(), recibirá Sale una pizza al horno de leña. Nadie escribe la línea que crea el horno ni la que crea la pizzería. Al compilar, Hilt comprueba este grafo y no echa nada en falta.

Tres palabras del ejemplo y del ejercicio.

  • SingletonComponent::class es el nombre de una clase como valor: ::class señala la clase en sí, no una instancia. Aquí es la manera de decirle a Hilt «este componente».
  • abstract marca algo sin cuerpo: una función que solo declara cómo se llama y qué devuelve, y una clase que no se puede construir directamente. Con @Binds basta eso: Hilt escribe el resto.
  • lateinit var es un campo que declaras sin valor y rellena alguien de fuera antes de usarlo. Es la manera de recibir piezas cuando no eres tú quien construye la clase, como una Activity (la crea Android y no puedes pasarle nada por el constructor). Con Hilt se escribe @Inject lateinit var y se llama inyección en campo.
ejemplo mínimo: lateinit (con comentarios)
class Cocinero {
    lateinit var horno: Horno                  // promete: «lo rellenarán más tarde»
    fun cocinar() = horno.hornear()
}
 
fun main() {
    val cocinero = Cocinero()
    cocinero.horno = HornoDeLena()             // alguien de fuera rellena el campo
    println(cocinero.cocinar())
}

Con las piezas de la pizzería (Horno y HornoDeLena) de antes, la consola muestra:

pizza al horno de leña

Si lees el campo antes de que nadie lo rellene, Kotlin se queja con lateinit property horno has not been initialized. Lo verás en una de las trampas de abajo.

Prepara el proyecto · KSP, Hilt y las dos entradas

Objetivo. Instalar KSP y Hilt en :app y conectar a Hilt la Application y la Activity. Aquí no se cambia ningún comportamiento: compruebas que compila y que Hilt ha escrito su código.

Gradle Scripts
libs.versions.toml (Version Catalog "libs")se modifica: [versions], [libraries] y [plugins]
build.gradle.kts (Project: Marcador)se modifica: plugins, con apply false
build.gradle.kts (Module :app)se modifica: plugins y dependencies
app → kotlin+java
com.sebastiannarvaez.marcador
MarcadorApplication.ktse modifica
MainActivity.ktse modifica
Los cinco ficheros de esta preparación, tal como los ves en la vista Android.

Sigue la receta de Los ficheros de Gradle de tu proyecto. Estrena un procesador de KSP (ksp(…)) y un plugin que no se llama como Kotlin.

1 · Qué instalas. Tres piezas.

  • Plugin de KSP (com.google.devtools.ksp): enseña a Gradle a ejecutar procesadores al compilar.
  • Plugin de Hilt (com.google.dagger.hilt.android): prepara a Hilt dentro de la compilación de la app.
  • Librería de Hilt (hilt-android) y su procesador (hilt-compiler): la librería viaja dentro de la app; el procesador solo corre al compilar y escribe el código del grafo.

Tu proyecto ya usa AGP 9.4.1. Usa Hilt 2.60.1, que funciona con Kotlin 2.4.20, AGP 9.4.1 y KSP 2.3.12 sin ninguna opción extra.

2 · Catálogo. Abre Gradle Scripts → libs.versions.toml (Version Catalog "libs") y añade las líneas al final de cada sección:

catálogo: líneas nuevas (con comentarios)
[versions]
# KSP ya no depende de la versión de Kotlin (desde la 2.3.0): lleva su propia línea.
# No pongas el formato viejo (2.2.21-2.0.5)
ksp = "2.3.12"
# Hilt (Dagger Hilt): librería, procesador y plugin comparten versión
hilt = "2.60.1"
 
[libraries]
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
# El procesador va en [libraries] porque es una dependencia de Maven más; la pedirá ksp(…)
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
 
[plugins]
# Cada plugin con su id oficial y su propia versión
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

3 · Raíz, con apply false. Abre Gradle Scripts → build.gradle.kts (Project: Marcador) y añade dos líneas al final del bloque plugins. La raíz «carga» cada plugin una sola vez para todo el proyecto, sin aplicarlo, porque no compila nada:

build.gradle.kts (Project: Marcador): líneas nuevas (con comentarios)
plugins {
    // … los plugins que ya tienes, sin tocar
    alias(libs.plugins.ksp) apply false     // nueva: KSP, cargado sin aplicar
    alias(libs.plugins.hilt) apply false    // nueva: Hilt, igual
}

4 · Módulo :app. Abre Gradle Scripts → build.gradle.kts (Module :app). Dos cambios: dos plugins (sin apply false) que añades al final de tu bloque plugins, sin sustituirlo, y dos dependencias al principio de dependencies:

build.gradle.kts (Module :app): lo nuevo (con comentarios)
plugins {
    // … los plugins que ya tienes, sin tocar
    alias(libs.plugins.ksp)     // aplica KSP a este módulo
    alias(libs.plugins.hilt)    // aplica el plugin de Hilt
}
 
dependencies {
    implementation(libs.hilt.android)   // la librería: viaja dentro de la app
    // Línea clave: «ksp» (la crea el plugin de KSP) se ejecuta al compilar y se queda fuera
    // de la app. Sin ella nadie escribe el código del grafo de Hilt
    ksp(libs.hilt.compiler)
    // … el resto de tus dependencias, sin tocar
}

5 · Sync Now. Pulsa Sync Now. Debe terminar sin errores rojos; la primera vez descarga dos plugins y dos librerías, así que necesita internet y tarda más.

6 · Las dos entradas. Hilt necesita saber dónde empieza. Abre MarcadorApplication.kt y MainActivity.kt con doble clic en el panel de proyecto (con el selector en Android, dentro de app → kotlin+java → com.sebastiannarvaez.marcador). En cada uno, el import va con los demás y la anotación justo encima de la línea class …; el resto del fichero no cambia.

Intenta escribirlo tú: la Application marcada como el punto donde arranca Hilt, y la Activity marcada como un lugar donde Hilt puede entregar piezas.

Intenta primero. Cuando termines, compara con la solución.Mostrar el código
app/src/main/java/com/sebastiannarvaez/marcador/MarcadorApplication.kt
import dagger.hilt.android.HiltAndroidApp
 
// @HiltAndroidApp: aquí arranca Hilt. Al compilar genera Hilt_MarcadorApplication, que
// crea el contenedor de toda la app (el SingletonComponent) al abrirse el proceso.
@HiltAndroidApp
class MarcadorApplication : Application() {
app/src/main/java/com/sebastiannarvaez/marcador/MainActivity.kt
import dagger.hilt.android.AndroidEntryPoint
 
// @AndroidEntryPoint: esta Activity puede recibir piezas de Hilt (y las pantallas que cuelgan
// de ella, también). Sin ella, Hilt no la conoce.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

7 · Comprueba. Que Sync termine bien no prueba que Hilt funcione. Abre la Terminal (View → Tool Windows → Terminal) y compila:

./gradlew :app:assembleGratisDebug

Debe terminar en BUILD SUCCESSFUL. La Terminal de Android Studio borra del pantallazo las líneas > Task … según avanzan, así que al final solo ves BUILD SUCCESSFUL y el número de tareas. Si quieres verlas (kspGratisDebugKotlin es KSP trabajando y hiltJavaCompileGratisDebug, Hilt escribiendo su código sobre la variante gratisDebug, la de Gradle: flavors y variantes), añade --console=plain al final del comando.

Para ver lo que Hilt ha escrito, lista la carpeta donde deja el código generado para tu Application:

ls app/build/generated/hilt/component_sources/gratisDebug/com/sebastiannarvaez/marcador

Debe salir Hilt_MarcadorApplication.java (junto a otros dos ficheros). En la vista Android no se ve: vive en build/, que no se muestra. Tu MarcadorApplication pasa a heredar de esa clase: el plugin de Hilt hace ese cambio al compilar, sin que escribas nada.

Tu primer grafo: la cafetería

Con Hilt instalado, lo pruebas fuera de Marcador, con piezas inventadas.

Reto · Ejercicio: la cafetería

En la carpeta ejercicios de tu proyecto (la que creaste en La rotación y la muerte del proceso), crea la subcarpeta cafeteria: clic derecho sobre com.sebastiannarvaez.marcador → New → Package → añade .ejercicios.cafeteria al final del nombre que aparece ya escrito. Después, clic derecho sobre esa carpeta → New → Kotlin Class/File → File → Cafeteria. La primera línea del fichero debe ser package com.sebastiannarvaez.marcador.ejercicios.cafeteria.

En Cafeteria.kt escribe, para Hilt:

  1. Una interfaz Molinillo con una función, moler, que devuelve un texto.
  2. Una clase MolinilloElectrico que cumple Molinillo y devuelve «molinillo eléctrico». Hilt debe saber construirla sin recetas.
  3. Una clase Cafetera, única en toda la app, que recibe un Molinillo y tiene una función, preparar, que devuelve «Café molido con » seguido de lo que devuelve moler.
  4. Un módulo, instalado en el componente de toda la app, que diga que quien pida un Molinillo recibe un MolinilloElectrico.

Para verlo. Hilt no funciona en el panel de @Preview: necesita la app arrancada. Por eso este ejercicio se ve en Logcat, con dos líneas temporales en MainActivity.kt. Una propiedad dentro de la clase, y una línea de Log.d justo debajo de super.onCreate(savedInstanceState):

líneas temporales de MainActivity (se quitan al terminar)
    @Inject lateinit var cafetera: Cafetera      // dentro de la clase: Hilt rellena este campo al crear la Activity
 
    // dentro de onCreate, debajo de super.onCreate(savedInstanceState):
    Log.d("Cafeteria", cafetera.preparar())

Es la inyección en campo que acabas de ver con Cocinero. Los import de Inject (javax.inject.Inject) y de Cafetera los propone Android Studio: pulsa Option+Enter en Mac o Alt+Enter en Windows y Linux sobre la palabra en rojo.

Ejecuta la app con Run y, en View → Tool Windows → Logcat, escribe tag:Cafeteria en el filtro. Debe salir esta línea (puede repetirse: Android vuelve a crear la Activity, por ejemplo al girar el móvil, y cada vez la rellena Hilt):

D Cafeteria: Café molido con molinillo eléctrico

Después, quita las dos líneas temporales de MainActivity.kt y deja la carpeta cafeteria. Antes, un experimento: borra la línea que une Molinillo con MolinilloElectrico y compila. ¿Qué dice Hilt, y en qué momento?

Ver solución
app/src/main/java/com/sebastiannarvaez/marcador/ejercicios/cafeteria/Cafeteria.kt
package com.sebastiannarvaez.marcador.ejercicios.cafeteria
 
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import javax.inject.Singleton
 
interface Molinillo { fun moler(): String }
 
class MolinilloElectrico @Inject constructor() : Molinillo {
    override fun moler() = "molinillo eléctrico"
}
 
@Singleton
class Cafetera @Inject constructor(private val molinillo: Molinillo) {
    fun preparar() = "Café molido con ${molinillo.moler()}"
}
 
@Module
@InstallIn(SingletonComponent::class)
abstract class CafeteriaModule {
    @Binds
    abstract fun molinillo(impl: MolinilloElectrico): Molinillo
}

Cafetera pide un Molinillo (una interfaz) y Hilt busca quién lo da: el @Binds del módulo, que apunta a MolinilloElectrico, que Hilt sabe construir por su @Inject constructor(). @Singleton hace que todos reciban la misma cafetera. Va aquí sobre la clase, porque tiene @Inject constructor; en el paso de abajo irá sobre una receta @Provides. Las dos formas valen y hacen lo mismo.

El experimento. Sin el @Binds, la compilación falla en :app:hiltJavaCompileGratisDebug, antes de que la app llegue a ejecutarse:

error: [Dagger/MissingBinding] com.sebastiannarvaez.marcador.ejercicios.cafeteria.Molinillo cannot be provided without an @Provides-annotated method.
      com.sebastiannarvaez.marcador.ejercicios.cafeteria.Molinillo is injected at
          [...SingletonC] com.sebastiannarvaez.marcador.ejercicios.cafeteria.Cafetera(molinillo)
      com.sebastiannarvaez.marcador.ejercicios.cafeteria.Cafetera is injected at
          [...ActivityC] com.sebastiannarvaez.marcador.MainActivity.cafetera

Léelo así: la primera línea dice qué falta (un Molinillo); las de debajo cuentan quién lo pidió, empezando por el más cercano (la Cafetera) y subiendo hasta el punto de entrada (MainActivity.cafetera). Los trozos entre corchetes, como [...SingletonC], son abreviaturas del componente donde Hilt busca. Es el revisor del grafo trabajando. Pon el @Binds otra vez.

Paso · RegistrarGol y el primer módulo

Objetivo. Que Hilt sepa construir RegistrarGol y entregar el repositorio de partidos. La app se comporta igual: los ViewModel todavía no le piden nada a Hilt.

app → kotlin+java → com.sebastiannarvaez.marcador
di
DatosModule.ktse crea
feature → marcador → domain
RegistrarGol.ktse modifica
Un fichero nuevo y uno que se modifica, en la vista Android.

Crea el paquete y el fichero. di es el nombre que usan Google y la mayoría de proyectos para «inyección de dependencias». Clic derecho sobre com.sebastiannarvaez.marcador (el que no lleva (test) ni (androidTest)) → New → Package. El campo ya trae com.sebastiannarvaez.marcador: escribe .di al final (con el punto delante) y pulsa Intro. Después, clic derecho sobre di → New → Kotlin Class/File → File → DatosModule. Se llama DatosModule porque entrega piezas de la capa de datos; más adelante le añadirás la base de datos. Hay un solo di para toda la app, no uno por feature, porque Hilt monta un único grafo con todas las recetas.

Piezas nuevas de este paso:

  • class RegistrarGol @Inject constructor(…): lo mismo que HornoDeLena @Inject constructor() del ejemplo. Hilt busca un PartidosRepository y se lo pasa.
  • object DatosModule con @Module y @InstallIn(SingletonComponent::class): un módulo de recetas. Es un object porque tendrá funciones con @Provides.
  • @Provides @Singleton fun partidos(): PartidosRepository = …: la receta. ¿Por qué @Provides y no @Binds? @Binds pide que la implementación tenga @Inject constructor, y PartidosRepositoryEnMemoria se construye con parámetros que ya traen un valor por defecto (la lista de equipos y la de partidos de ejemplo). Si le pones @Inject, Hilt ignora esos valores y te pide cada parámetro al grafo, donde nadie lo da. Con la receta escrita a mano llamas tú al constructor sin parámetros y quedan los valores por defecto. El tipo que devuelve es la interfaz: quien pida un PartidosRepository recibe esa implementación.
  • @Singleton: una sola instancia en toda la app. Todos los que pidan el repositorio reciben el mismo y ven los mismos goles. Sin ella, cada petición construiría uno nuevo. Aquí va sobre la receta; en la cafetería iba sobre la clase.

Intenta escribirlo tú, en este orden:

  1. RegistrarGol construida por Hilt a partir de su constructor.
  2. DatosModule, un módulo instalado en el componente de toda la app, con una receta que entrega un PartidosRepository único, de memoria.

Debes ver la app igual que antes: el primer partido en 3-2 y, tras «Gol Rayo FC», 4-2 en la lista.

Intenta primero. Cuando termines, compara con la solución.Mostrar el código
app/src/main/java/com/sebastiannarvaez/marcador/di/DatosModule.kt
package com.sebastiannarvaez.marcador.di
 
import com.sebastiannarvaez.marcador.feature.partidos.data.PartidosRepositoryEnMemoria
import com.sebastiannarvaez.marcador.feature.partidos.domain.PartidosRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
 
// UN MODULO de Hilt: recetas para lo que Hilt no sabe construir solo. Se llama
// DatosModule porque da piezas de la capa de datos (más adelante, también la base de datos).
//
// PartidosRepository es una interfaz (no se puede construir) y su implementación tiene
// parámetros con valor por defecto, que Hilt no entiende. Así que se escribe la receta a
// mano con @Provides.
//
// @InstallIn(SingletonComponent::class): estas recetas viven en el contenedor de la app,
// el que dura lo que el proceso.
@Module
@InstallIn(SingletonComponent::class)
object DatosModule {
    // @Singleton: UNA sola instancia para toda la app. Todos los que pidan el repositorio
    // reciben el mismo, y por eso ven los mismos goles.
    @Provides
    @Singleton
    fun partidos(): PartidosRepository = PartidosRepositoryEnMemoria()
}

En RegistrarGol.kt cambian el import y el encabezado de la clase (el resto del fichero no cambia):

app/src/main/java/com/sebastiannarvaez/marcador/feature/marcador/domain/RegistrarGol.kt
import javax.inject.Inject
 
// @Inject constructor: le dice a Hilt «así se construye esta clase; dame lo que pide entre
// paréntesis». Hilt busca un PartidosRepository en sus módulos y se lo pasa.
class RegistrarGol @Inject constructor(private val repositorio: PartidosRepository) {

Qué hace cada pieza:

PiezaQué hace
@HiltAndroidApp en MarcadorApplicationArranca Hilt al abrirse el proceso
@AndroidEntryPoint en MainActivityDeja que la Activity (y las pantallas que cuelgan de ella) reciban piezas
RegistrarGol @Inject constructorHilt la construye pidiendo el repositorio
DatosModule.partidos()La receta del repositorio, única en toda la app

Ningún ViewModel ha cambiado: siguen sacando el repositorio de Repositorios. Hilt ya sabe construir las piezas, pero nadie se las pide todavía. De eso se encarga Hilt y los ViewModels.

Probarlo. Ejecuta la app con Run (variante gratisDebug en View → Tool Windows → Build Variants y emulador arrancado, como en La Activity). Pulsa «Gol Rayo FC» y comprueba que el primer partido pasa de 3-2 a 4-2 en la pestaña Partidos.

Punto de control

  • ./gradlew :app:assembleGratisDebug termina en BUILD SUCCESSFUL (con --console=plain verás pasar kspGratisDebugKotlin y hiltJavaCompileGratisDebug).
  • ls app/build/generated/hilt/component_sources/gratisDebug/com/sebastiannarvaez/marcador muestra Hilt_MarcadorApplication.java, la clase que Hilt ha escrito.
  • En Logcat, el ejercicio de la cafetería mostró «Café molido con molinillo eléctrico», y ya no quedan en MainActivity.kt ni el campo cafetera ni el Log.d de prueba.
  • MarcadorApplication lleva @HiltAndroidApp, MainActivity lleva @AndroidEntryPoint y RegistrarGol tiene @Inject constructor.
  • La app se abre igual que antes y, tras «Gol Rayo FC», la fila del primer partido dice 4-2 en la pestaña Partidos.

Comprueba lo que entendiste

6 preguntas · necesitas 5 para completar la lección

  1. 1.¿Qué significa «inyectar» una dependencia en una clase?

  2. 2.¿Qué hace KSP cuando compilas un proyecto con Hilt?

  3. 3.¿Por qué DatosModule entrega PartidosRepositoryEnMemoria con @Provides en lugar de usar @Binds?

  4. 4.Quitas el @Binds que une Molinillo con MolinilloElectrico y compilas. ¿Qué ocurre?

  5. 5.¿Qué pasa si falta @AndroidEntryPoint en MainActivity y la Activity tiene un campo con @Inject lateinit var?

  6. 6.¿Qué consigue @Singleton en la receta del repositorio de partidos?

Faltan 6 de 6