Prepropaga la base de datos de Room

A veces, es posible que quieras que la app comience con una base de datos que ya esté cargada con un conjunto específico de datos. Esto se llama completar previamente una base de datos. En Room 2.2.0 y versiones posteriores, puedes usar métodos de API para prepropagar contenido de un archivo de base de datos preempaquetado en el sistema de archivos del dispositivo en una base de datos de Room.

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 al método createFromAsset() del objeto RoomDatabase.Builder antes de llamar a build():

Kotlin

Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build()

Java

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build();

El método createFromAsset() acepta un argumento de string que contiene una ruta 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 al método createFromFile() desde el objeto RoomDatabase.Builder antes de llamar a build():

Kotlin

Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromFile(File("mypath"))
    .build()

Java

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromFile(new File("mypath"))
    .build();

El método createFromFile() acepta un argumento File 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 preempaquetado con el mismo número que la versión de destino, Room intenta completar 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.

Obtén más información en Cómo migrar bases 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.

Kotlin

// Database class definition declaring version 3.
@Database(version = 3)
abstract class AppDatabase : RoomDatabase() {
    ...
}

// Destructive migrations are enabled and a prepackaged database
// is provided.
Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db")
    .fallbackToDestructiveMigration()
    .build()

Java

// Database class definition declaring version 3.
@Database(version = 3)
public abstract class AppDatabase extends RoomDatabase {
    ...
}

// Destructive migrations are enabled and a prepackaged database
// is provided.
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .fallbackToDestructiveMigration()
    .build();

Lo que ocurre en esta situación es lo siguiente:

  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 se llama al método del compilador fallbackToDestructiveMigration(), 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. Por otro lado, si el archivo de base de datos preempaquetado estuviera en la versión 2, Room observaría que no coincide con la versión de destino y no lo usaría como parte de 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:

Kotlin

// Database class definition declaring version 3.
@Database(version = 3)
abstract class AppDatabase : RoomDatabase() {
    ...
}

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

// A prepackaged database is provided.
Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db")
    .addMigrations(MIGRATION_2_3)
    .build()

Java

// Database class definition declaring version 3.
@Database(version = 3)
public abstract class AppDatabase extends RoomDatabase {
    ...
}

// Migration path definition from version 2 to version 3.
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        ...
    }
};

// A prepackaged database is provided.
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .addMigrations(MIGRATION_2_3)
    .build();

Lo que ocurre en esta situación es lo siguiente:

  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 el método migrate() definido 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.

Kotlin

// Database class definition declaring version 4.
@Database(version = 4)
abstract class AppDatabase : RoomDatabase() {
    ...
}

// Migration path definition from version 3 to version 4.
val MIGRATION_3_4 = object : Migration(3, 4) {
    override fun migrate(database: SupportSQLiteDatabase) {
        ...
    }
}

// Destructive migrations are enabled and a prepackaged database is
// provided.
Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db")
    .addMigrations(MIGRATION_3_4)
    .fallbackToDestructiveMigration()
    .build()

Java

// Database class definition declaring version 4.
@Database(version = 4)
public abstract class AppDatabase extends RoomDatabase {
    ...
}

// Migration path definition from version 3 to version 4.
static final Migration MIGRATION_3_4 = new Migration(3, 4) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        ...
    }
};

// Destructive migrations are enabled and a prepackaged database is
// provided.
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .addMigrations(MIGRATION_3_4)
    .fallbackToDestructiveMigration()
    .build();

Lo que ocurre en esta situación es lo siguiente:

  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 se llama al método del compilador fallbackToDestructiveMigration(), 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 tu 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 el método migrate() definido 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.

Recursos adicionales

Para obtener más información sobre cómo completar previamente una base de datos de Room, consulta los siguientes recursos adicionales.

Videos

Blogs