Prepropaga la base de datos de Room

Si quieres que la app comience con una base de datos que ya esté cargada con un conjunto específico de datos, puedes completar previamente la base de datos. En Room, puedes usar APIs para completar previamente una base de datos en la inicialización con contenido de un archivo de base de datos preempaquetado en el sistema de archivos del dispositivo.

Completa previamente desde un recurso de aplicación

Para completar previamente una base de datos de Room a partir de un archivo de base de datos preempaquetado que esté en cualquier lugar del directorio assets/ de la app, llama a la createFromAsset función del objeto RoomDatabase.Builder antes de llamar a build:

Room.databaseBuilder<AppDatabase>(appContext, "sample.db")
    .createFromAsset("database/myapp.db")
    .build()

La función createFromAsset acepta un argumento de cadena que contiene una ruta de acceso relativa del directorio assets/ al archivo de base de datos preempaquetado.

Cómo completar previamente desde el sistema de archivos

Para completar previamente una base de datos de Room a partir de un archivo de base de datos preempaquetado que esté en cualquier parte del sistema de archivos del dispositivo, excepto el directorio assets/ de la app, llama a la función createFromFile del objeto RoomDatabase.Builder antes de llamar a build:

Room.databaseBuilder<AppDatabase>(appContext, "sample.db")
    .createFromFile(File("mypath"))
    .build()

La función createFromFile acepta un File argumento para el archivo de base de datos preempaquetado. Room crea una copia del archivo designado en lugar de abrirlo directamente. Por lo tanto, debes asegurarte de que la app tenga permisos de lectura para el archivo.

Cómo controlar migraciones que incluyan bases de datos preempaquetadas

Los archivos de base de datos preempaquetados también pueden cambiar cómo tu base de datos de Room controla las migraciones de resguardo. Por lo general, cuando las migraciones destructivas están habilitadas y Room debe hacer una migración sin una ruta de migración, descarta todas las tablas de la base de datos y crea una base de datos vacía con el esquema especificado para la versión objetivo. Sin embargo, si incluyes un archivo de base de datos con el mismo número que la versión de destino, Room propaga la base de datos recién creada con el contenido del archivo de base de datos preempaquetado después de hacer la migración destructiva.

Para obtener más información sobre las migraciones de bases de datos de Room, consulta Cómo migrar tu base de datos de Room.

En las siguientes secciones, se presentan algunos ejemplos de cómo funciona esto en la práctica.

Ejemplo: Migración de resguardo con una base de datos preempaquetada

Supongamos lo siguiente:

  • Tu app define una base de datos de Room en la versión 3.
  • La instancia de base de datos ya instalada en el dispositivo está en la versión 2.
  • Hay un archivo de base de datos preempaquetado que está en la versión 3.
  • No se implementó la ruta de migración de la versión 2 a la 3.
  • Las migraciones destructivas están habilitadas.

// Database class definition declaring version 3.
@Database(entities = [SampleEntity::class], version = 3)
abstract class FallbackAppDatabase : RoomDatabase() {
    // ...
}

fun createFallbackDb(appContext: Context) {
    Room.databaseBuilder<FallbackAppDatabase>(appContext, "sample.db")
        .createFromAsset("database/myapp.db")
        .fallbackToDestructiveMigration()
        .build()
}

Esto es lo que sucede en esta situación:

  1. Como la base de datos definida en la app está en la versión 3 y la instancia de base de datos ya instalada en el dispositivo está en la versión 2, se necesita una migración.
  2. Como no hay un plan de migración implementado de la versión 2 a la versión 3, la migración es de resguardo.
  3. Como llamas a la fallbackToDestructiveMigrationfunción del compilador, la migración de resguardo es destructiva. Room descarta la instancia de base de datos que está instalada en el dispositivo.
  4. Como hay un archivo de base de datos preempaquetado que está en la versión 3, Room vuelve a crear la base de datos con el contenido de ese archivo. Si el archivo de base de datos preempaquetado está en la versión 2, Room determina que no coincide con la versión de destino y no lo usa para la migración de resguardo.

Ejemplo: Migración implementada con una base de datos preempaquetada

Supongamos que tu app implementa una ruta de migración de la versión 2 a la 3:

// Database class definition declaring version 3.
@Database(entities = [SampleEntity::class], version = 3)
abstract class ImplementedAppDatabase : RoomDatabase() {
    // ...
}

// Migration path definition from version 2 to version 3.
val MIGRATION_2_3 = object : Migration(2, 3) {
    override suspend fun migrate(connection: SQLiteConnection) {
        // ...
    }
}

fun createImplementedDb(appContext: Context) {
    Room.databaseBuilder<ImplementedAppDatabase>(appContext, "sample.db")
        .createFromAsset("database/myapp.db")
        .addMigrations(MIGRATION_2_3)
        .build()
}

Esto es lo que sucede en esta situación:

  1. Como la base de datos definida en la app está en la versión 3 y la base de datos ya instalada en el dispositivo está en la versión 2, se necesita una migración.
  2. Como hay una ruta de migración implementada de la versión 2 a la versión 3, Room ejecuta la función migrate definida para actualizar la instancia de base de datos en el dispositivo a la versión 3 y mantiene los datos que ya están en la base de datos. Room no usa el archivo de base de datos preempaquetado, ya que solo los usa en el caso de una migración de resguardo.

Ejemplo: Migración de varios pasos con una base de datos preempaquetada

Los archivos de base de datos preempaquetados también pueden afectar las migraciones que constan de varios pasos. Considera el siguiente caso:

  • Tu app define una base de datos de Room en la versión 4.
  • La instancia de base de datos ya instalada en el dispositivo está en la versión 2.
  • Hay un archivo de base de datos preempaquetado que está en la versión 3.
  • Hay una ruta de migración implementada de la versión 3 a la versión 4, pero no de la versión 2 a la 3.
  • Las migraciones destructivas están habilitadas.

// Database class definition declaring version 4.
@Database(entities = [SampleEntity::class], version = 4)
abstract class MultiStepAppDatabase : RoomDatabase() {
    // ...
}

val MIGRATION_3_4 = object : Migration(3, 4) {
    override suspend fun migrate(connection: SQLiteConnection) {
        // ...
    }
}

fun createMultiStepDb(appContext: Context) {
    Room.databaseBuilder<MultiStepAppDatabase>(appContext, "sample.db")
        .createFromAsset("database/myapp.db")
        .addMigrations(MIGRATION_3_4)
        .fallbackToDestructiveMigration()
        .build()
}

Esto es lo que sucede en esta situación:

  1. Como la base de datos definida en la app está en la versión 4 y la instancia de base de datos ya instalada en el dispositivo está en la versión 2, se necesita una migración.
  2. Como no hay una ruta de migración implementada de la versión 2 a la versión 3, la migración es de resguardo.
  3. Como llamas a la fallbackToDestructiveMigrationfunción del compilador, la migración de resguardo es destructiva. Room descarta la instancia de base de datos en el dispositivo.
  4. Como hay un archivo de base de datos preempaquetado que está en la versión 3, Room vuelve a crear la base de datos con el contenido de ese archivo.
  5. La base de datos instalada en el dispositivo ahora está en la versión 3. Como aún es inferior a la versión definida en la app, se necesita otra migración.
  6. Como hay una ruta de migración implementada de la versión 3 a la 4, Room ejecuta la función migrate definida para actualizar la instancia de base de datos del dispositivo a la versión 4 y mantiene los datos que se copiaron del archivo de base de datos preempaquetado de la versión 3.