Cuando cierras una aplicación Android y el sistema operativo la destruye, toda la información almacenada en memoria se pierde. Las tareas, los estados, todo vuelve a un punto inicial. Room es la solución más utilizada para resolver este problema, permitiendo guardar datos de forma persistente en una base de datos local SQLite. A continuación se detalla paso a paso cómo migrar de un fake local data source a uno real con Room, incluyendo la creación de entidades, DAOs y la inyección manual de dependencias.
¿Cómo se configura Room en el proyecto?
El primer paso es activar las dependencias necesarias en los archivos de configuración. En el archivo build.gradle a nivel de módulo, se descomentaron las líneas correspondientes a KSP y las librerías de Room [0:30]. Lo mismo se hizo en el build.gradle a nivel de proyecto, habilitando los plugins de KSP y Room. Tras sincronizar el proyecto, las dependencias quedan listas para usar.
Dentro de la carpeta data, se crea la clase TodoDatabase, que es una abstract class que extiende de RoomDatabase [1:07]. Esta clase se marca con la anotación @Database, indicando las entidades que contiene y la versión de la base de datos.
¿Qué son las entidades y el DAO en Room?
Una entidad en Room representa una tabla dentro de la base de datos. Se creó TaskEntity marcada con la anotación @Entity y un table name de "tasks" [1:24]. El modelado incluye:
- Un campo
id marcado como @PrimaryKey con autoGenerate = false, ya que el identificador se genera externamente.
- Un
title obligatorio.
- Una
description opcional.
- Un campo
isCompleted, convertido de camelCase a snake_case usando @ColumnInfo(name = "is_completed").
- Un campo
date de tipo Long, ya que Room no soporta directamente objetos Date sin un type converter [2:12].
Para la conversión entre modelos se crearon dos funciones. La función fromTask dentro de un companion object transforma un Task en TaskEntity, convirtiendo la fecha a epoch millis mediante ZoneId.systemDefault() y toEpochMilli() [2:40]. La función inversa toTask() reconstruye un Task usando Instant.ofEpochMilli() y ZoneId.systemDefault() [3:16].
El DAO (Data Access Object) es una interfaz marcada con @Dao que define las operaciones sobre la tabla [3:52]. Las operaciones implementadas fueron:
getAllTasks: query de selección completa.
getTaskById: búsqueda por identificador.
upsert: operación que funciona como update o insert, útil para crear o actualizar tareas.
deleteTaskById: eliminación por ID.
deleteAllTasks: borrado completo de la tabla.
¿Cómo se implementa el data source real y la inyección manual?
Se creó RoomTaskLocalDataSource, que recibe un TaskDao y un dispatcher para ejecutar operaciones en un hilo de background [5:00]. Esto es fundamental: las operaciones de base de datos no pueden ejecutarse en el hilo principal (UI). Cada función utiliza withContext(Dispatchers.IO) para cambiar al hilo adecuado.
getAllTasks mapea las entidades a modelos de dominio con flowOn(Dispatchers.IO).
addTask y updateTask convierten el modelo con TaskEntity.fromTask() antes de insertar.
removeTask y removeAllTasks invocan las funciones de borrado del DAO.
¿Cómo se crean los ViewModels con dependencias manuales?
Sin un framework de inyección de dependencias, la construcción de ViewModels con dependencias requiere un companion object con una variable factory [7:08]. Este factory le indica al ViewModel cómo instanciarse con las dependencias necesarias: un SavedStateHandle y el TaskLocalDataSource.
Se creó una clase TodoApplication que hereda de Application [8:00]. Esta clase global contiene las variables de dispatcher (Dispatchers.IO) y el data source, construido mediante un DataSourceFactory. Este factory invoca TodoDatabase.getDatabase(context) para obtener la instancia de la base de datos como Singleton usando una variable @Volatile [6:15].
¿Qué cambios se hacen en la navegación?
Los ViewModels se extrajeron de las pantallas individuales y se movieron al nivel de navegación en NavigationRoot [10:36]. Allí se crean con su factory correspondiente y se pasan como parámetro a cada pantalla. Este patrón prepara el terreno para la inyección de dependencias con Hilt en la siguiente clase.
Al ejecutar la aplicación, la lista aparece vacía inicialmente. Pero al crear tareas y cerrar la aplicación, la información persiste al reabrirla [11:28]. El cambio de una fuente de datos volátil a una persistente tiene un costo: toda la inicialización manual de dependencias. Hilt simplificará ese proceso significativamente.
¿Has implementado Room en tus proyectos? Comparte tu experiencia o dudas sobre la migración de datos volátiles a persistentes.