Curso de Patrones MVVM en Android

Hilt y SharedPreferences en ViewModels Android

Curso de Patrones MVVM en Android

Hilt y SharedPreferences en ViewModels Android

Resumen

Configurar persistencia de datos con SharedPreferences y inyección de dependencias con Hilt en una app Android te permite guardar las preferencias del usuario, personalizar su experiencia y mantener un código desacoplado y fácil de testear. Si estás construyendo una app con Jetpack Compose y arquitectura por features, esta guía te muestra cómo conectar ambos conceptos en los ViewModels de un módulo de onboarding.

¿Por qué importa la persistencia de datos en una app Android?

La persistencia permite que la app recuerde decisiones del usuario entre sesiones. En el proyecto de calorías, guardar género, edad, altura, peso, nivel de actividad y objetivos nutricionales evita que la persona repita el onboarding cada vez que abre la aplicación [01:20].

SharedPreferences es el mecanismo que ofrece Android para almacenar datos primitivos y preferencias del usuario en un archivo del sistema. Funciona bien para valores simples como strings, booleans o enteros, y es ideal para banderas como shouldShowOnboarding.

¿Qué es SharedPreferences? Es una API de Android que guarda pares clave-valor en un archivo privado del dispositivo. Sirve para datos pequeños como configuraciones, banderas o preferencias del usuario, no para grandes volúmenes de información.

¿Cómo inyectar SharedPreferences en cada ViewModel?

Antes de usar Hilt, el primer paso fue inyectar manualmente las preferencias en el constructor de cada ViewModel del onboarding. La idea es que cada pantalla guarde su dato cuando el usuario presione next.

En el GenderViewModel, al hacer clic en next se llama a preferences.saveGender(selectedGender) para conservar la última selección [02:30]. El mismo patrón se replica en los demás ViewModels:

  • AgeViewModel inyecta preferences y el caso de uso filterOutDigits, y llama a preferences.saveAge(ageNumber).
  • HeightViewModel y WeightViewModel siguen el mismo flujo con saveHeight y saveWeight.
  • ActivityLevelViewModel solo necesita las preferencias y guarda selectedActivityLevel.
  • NutrientGoalViewModel guarda carbRatio, protein y fat antes de navegar al TrackerScreen.

Un detalle práctico: cuando el campo de texto entrega un string y la función espera un Int, conviene crear una variable intermedia como val ageNumber = age.toIntOrNull() ?: 0 para evitar errores de tipo.

¿Qué hace el caso de uso filterOutDigits?

Es un caso de uso que filtra la entrada del campo de texto para que solo acepte dígitos válidos. Inyectarlo en lugar de instanciarlo dentro del ViewModel sigue el principio de separación de responsabilidades y deja la clase lista para pruebas unitarias.

¿Cómo configurar Hilt como inyector de dependencias?

La inyección de dependencias es un patrón donde las clases reciben sus dependencias desde fuera en lugar de crearlas. Esto reduce el acoplamiento y facilita el testing. Hilt es la solución oficial de Android construida sobre Dagger.

Dentro del paquete app se crea un subpaquete di y un objeto AppModule anotado con @Module y @InstallIn(SingletonComponent::class) [10:15]. El patrón Singleton garantiza una única instancia reutilizable en todo el proyecto.

¿Qué es un Singleton en Hilt? Es un componente único que vive durante toda la app. Hilt lo crea una sola vez y lo inyecta donde se necesite, evitando duplicar instancias y optimizando el uso de memoria.

El AppModule provee tres dependencias clave:

  1. provideFilterOutDigitsUseCase() que retorna el caso de uso.
  2. providePreferences(sharedPreferences) que retorna la implementación de preferencias del proyecto.
  3. provideSharedPreferences(app: Application) que llama a app.getSharedPreferences("shared_pref", MODE_PRIVATE).

¿Cómo conectar Hilt con la aplicación?

Se crea una clase PlatziCaloriesApp : Application() anotada con @HiltAndroidApp. Esta anotación es el punto de entrada que le indica a Hilt que la app usará inyección de dependencias [13:40].

Después hay que registrar la clase en el AndroidManifest.xml con android:name. Sin este paso, Hilt no se inicializa al instalar la app.

¿Cómo anotar los ViewModels y la MainActivity con Hilt?

Cada ViewModel recibe dos anotaciones: @HiltViewModel sobre la clase y @Inject constructor(...) en el constructor. Con esto, Hilt resuelve automáticamente las dependencias declaradas en AppModule.

La MainActivity se anota con @AndroidEntryPoint para convertirla en el punto de entrada real de la UI. Allí se declara @Inject lateinit var preferences: Preferences y dentro de onCreate se calcula shouldShowOnboarding = preferences.loadShouldShowOnboarding() [16:50].

¿Qué librería se necesita para usar HiltViewModel en Compose Navigation?

Para que la navegación entregue un ViewModel gestionado por Hilt se agrega la dependencia androidx.hilt:hilt-navigation-compose:1.2.0 en el archivo de versiones y en el build.gradle del módulo app.

Con esa librería, en cada Composable de pantalla se reemplaza viewModel: GenderViewModel = viewModel() por viewModel: GenderViewModel = hiltViewModel(). El NavigationRoot queda más limpio porque ya no necesita instanciar manualmente cada ViewModel.

¿Cómo decidir si mostrar el onboarding al iniciar la app?

En el NavigationRoot se agrega un parámetro shouldShowOnboarding: Boolean. Si es true, el startDestination apunta a WelcomeScreenRoot; si es false, navega directo a TrackerOverviewScreenRoot.

Esa bandera se lee desde las preferencias en la MainActivity antes de pintar el grafo de navegación. El resultado: la primera vez el usuario completa el flujo de género, edad, altura, peso, nivel de actividad y objetivo nutricional; en la segunda apertura aterriza directo en la home.

Como buena práctica, centraliza la gestión de datos en una capa dedicada y deja que los ViewModels interactúen con ella. Así mantienes el código desacoplado, testeable y listo para escalar.

¿Ya probaste mover tu inyección manual a Hilt? Cuéntame en los comentarios qué ViewModel fue el más difícil de migrar.