Guía Completa para realizar Migraciones de Bases de Datos con Room

  • Diferenciación entre el uso de rutas de migración automáticas para cambios simples y manuales para lógicas complejas.
  • Importancia de la exportación del esquema en formato JSON para validar la estructura y facilitar las pruebas.
  • Implementación de estrategias de resguardo destructivo y pre-carga de datos desde archivos preempaquetados.

Migraciones de Bases de Datos con Room

Cuando desarrollas una aplicación, es totalmente normal que las necesidades del proyecto evolucionen. A medida que añades funcionalidades, te darás cuenta de que las clases de entidad de Room y sus tablas correspondientes deben cambiar para adaptarse a los nuevos requisitos. El gran reto aquí es lograr que el usuario no pierda ni un solo dato cuando actualiza la app y el esquema de la base de datos ha variado.

Para solucionar esto, Room ofrece un abanico de opciones que van desde lo más sencillo hasta implementes más artesanales. Dependiendo de si el cambio es un simple añadido de columna o una reestructuración profunda, puedes optar por migraciones automáticas o manuales, asegurando siempre que la transición sea transparente y no provoque que la aplicación se cierre inesperadamente.

El camino rápido: Migraciones Automáticas

A partir de la versión 2.4.0-alpha01, Room nos facilita la vida con las migraciones automáticas. Básicamente, el framework analiza la diferencia entre la versión anterior y la nueva y genera el plan de migración por nosotros. Para activarlo, basta con añadir la anotación @AutoMigration dentro de la propiedad autoMigrations en el decorador @Database.

Ojo, hay un detalle fundamental: para que esto funcione, exportSchema debe estar configurado en true. Si no has exportado el esquema o no has compilado la base de datos con el nuevo número de versión, Room no sabrá qué ha cambiado y la migración fallará estrepitosamente.

Migraciones de Bases de Datos con Room
Artículo relacionado:
Guía Completa sobre Relaciones y Consultas Complejas en Room y Bases de Datos Relacionales

Casos complejos en el modo automático

A veces, Room se queda un poco corto y detecta cambios ambiguos, como cuando decides borrar una tabla o cambiarle el nombre a una columna. En estos casos, el compilador te soltará un error y te pedirá que implementes un AutoMigrationSpec. Esta clase estática es donde le das a Room la información extra que necesita para no perderse.

Dentro de este Spec, puedes usar anotaciones como @RenameTable para guiar al sistema. Además, si necesitas ejecutar código extra una vez que la migración automática ha terminado, tienes a tu disposición el método onPostMigrate(), que es ideal para realizar ajustes finales de datos.

Control total: Migraciones Manuales

Hay situaciones donde la automatización no llega, como cuando necesitas dividir la información de una tabla en dos entidades distintas. Aquí es donde entran las migraciones manuales mediante la creación de una clase que herede de Migration y donde sobrescribas el método migrate().

En este método, dispones de un objeto SupportSQLiteDatabase que te permite ejecutar sentencias SQL directamente. Por ejemplo, para añadir una columna, usarías un ALTER TABLE. Es vital que al registrar estas rutas en el builder de la base de datos uses el método addMigrations().

Un consejo de oro: evita usar constantes de Kotlin dentro de las consultas SQL de migración; escribe consultas SQL completas. Esto evita errores catastróficos si en el futuro cambias el valor de una constante pero la migración antigua sigue necesitando el valor original.

Estrategias avanzadas y gestión de errores

Si te encuentras en una situación donde no puedes definir una ruta de migración y no te importa que los datos se borren (porque quizá son solo una caché), puedes usar fallbackToDestructiveMigration(). Esto hace que Room borre todo y recree las tablas desde cero, evitando que la app crashee con una IllegalStateException.

Para un control más fino, existen alternativas como fallbackToDestructiveMigrationFrom(), que solo borra datos si el usuario viene de versiones muy específicas, o fallbackToDestructiveMigrationOnDowngrade(), útil cuando alguien instala una versión más antigua de la app sobre una nueva.

Bases de datos preempaquetadas

A veces queremos que la app ya venga con datos de serie. Room permite hacer esto mediante createFromAsset() o createFromFile(). Lo interesante es cómo interactúa esto con las migraciones destructivas: si tienes un archivo preempaquetado que coincida con la versión de destino, Room lo usará para rellenar la base de datos después de haber realizado una limpieza destructiva.

El arte de renombrar y manejar NOT NULL

Renombrar columnas en SQLite puede ser un dolor de cabeza, especialmente en versiones anteriores a la API 29. El truco maestro aquí es el patrón crear-copiar-eliminar: creas una tabla nueva con el nombre correcto, pasas los datos con un INSERT INTO … SELECT, y finalmente borras la tabla vieja con un DROP TABLE.

Otro escenario crítico es añadir una columna NOT NULL sin valor predeterminado en una tabla que ya tiene registros. Como SQLite no permite esto directamente, debes crear una tabla temporal con un valor default provisional, mover los datos, borrar la original y renombrar la temporal a la definitiva.

Asegurando la estabilidad: Testing y Esquemas

No lances una migración a producción sin probarla. Room ofrece el artefacto room-testing y la clase MigrationTestHelper. Con esto puedes crear una base de datos en la versión antigua, insertar datos reales y luego ejecutar runMigrationsAndValidate() para comprobar que el esquema final es el esperado y que los datos siguen ahí.

Para que todo esto sea viable, es imprescindible gestionar los archivos JSON del esquema. Configurando el room.schemaLocation en el archivo gradle, Room generará un historial de versiones. Estos archivos son la verdad absoluta: el campo createSql del JSON es exactamente lo que debes replicar en tus migraciones manuales para evitar discrepancias.

Transición desde SQLite puro a Room

Si vienes de usar SQLite de la forma tradicional, el salto a Room es muy beneficioso. El proceso implica convertir tus modelos en @Entity, definir DAOs para sustituir tus queries manuales y crear una clase que extienda de RoomDatabase. Es fundamental incrementar la versión de la base de datos y definir una ruta de migración, aunque sea vacía, para que Room no borre los datos existentes al tomar el control.

Migraciones de Bases de Datos con Room
Artículo relacionado:
Guía Completa para Crear Bases de Datos Locales con Room en Android

Dominar el ciclo de vida de los esquemas, desde la exportación de JSON y la redacción de SQL preciso hasta la validación mediante tests instrumentados, permite que la aplicación evolucione sin miedo a romper la experiencia del usuario ni perder información valiosa en el dispositivo. Comparte esta información para que más personas conozcan del tema.


Add as preferred source