Preencher automaticamente o banco de dados do Room

Às vezes, você pode querer que seu app comece com um banco de dados que já esteja carregado com um conjunto específico de dados. Isso é chamado de preenchimento automático de um banco de dados. No Room 2.2.0 e versões mais recentes, você pode usar métodos de API para preencher automaticamente um banco de dados do Room na inicialização com conteúdo de um arquivo de banco de dados pré-empacotado no sistema de arquivos do dispositivo.

Preencher automaticamente usando um recurso de app

Para preencher automaticamente um banco de dados do Room usando um arquivo de banco de dados pré-empacotado localizado em qualquer lugar do diretório assets/, chame o método createFromAsset() do objeto RoomDatabase.Builder antes de chamar 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();

O método createFromAsset() aceita um argumento de string que contém um caminho relativo do diretório assets/ para o arquivo de banco de dados pré-empacotado.

Preencher automaticamente usando o sistema de arquivos

Para preencher automaticamente um banco de dados do Room usando um arquivo de banco de dados pré-empacotado localizado em qualquer lugar no sistema de arquivos do dispositivo, exceto no diretório assets/ do app, chame o método createFromFile() do objeto RoomDatabase.Builder antes de chamar 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();

O método createFromFile() aceita um argumento File para o arquivo de banco de dados pré-empacotado. Em vez de abrir o arquivo designado diretamente, o Room cria uma cópia dele. Portanto, verifique se o app tem permissões de leitura no arquivo.

Processar migrações que incluem bancos de dados pré-empacotados

Os arquivos de banco de dados pré-empacotados também podem mudar a maneira como o banco de dados do Room gerencia migrações de substituto. Normalmente, quando as migrações destrutivas são ativadas e o Room precisa realizar uma migração sem um caminho, ele descarta todas as tabelas no banco de dados e cria um banco vazio com o esquema especificado para a versão de destino. No entanto, se você incluir um arquivo de banco de dados pré-empacotado com o mesmo número da versão de destino, o Room tenta preencher o banco de dados recém-recriado com o conteúdo desse arquivo após realizar a migração destrutiva.

Para mais informações sobre as migrações de banco de dados do Room, consulte Como migrar bancos de dados do Room.

As seções abaixo apresentam alguns exemplos de como isso funciona na prática.

Exemplo: migração de substituto com um banco de dados pré-empacotado

Suponha que:

  • seu app define um banco de dados do Room na versão 3.
  • a instância do banco de dados instalada no dispositivo está na versão 2.
  • há um arquivo de banco de dados pré-empacotado que está na versão 3;
  • não há caminho de migração implementado da versão 2 para a versão 3.
  • as migrações destrutivas estão ativadas.

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();

Veja o que acontece nesta situação:

  1. Como o banco de dados definido no seu app está na versão 3 e a instância do banco de dados já instalada no dispositivo está na versão 2, é necessário fazer uma migração.
  2. Como não há um plano de migração implementado da versão 2 para a versão 3, trata-se de uma migração de substituto.
  3. Como o método de builder fallbackToDestructiveMigration() é chamado, a migração de substituto é destrutiva. O Room descarta a instância do banco de dados instalada no dispositivo.
  4. Como há um arquivo do banco de dados pré-empacotado que está na versão 3, o Room recria o banco de dados e o preenche usando o conteúdo do arquivo pré-empacotado. Por outro lado, se o arquivo do banco de dados pré-empacotado estivesse na versão 2, o Room notaria que ele não corresponde à versão de destino e não o usaria como parte da migração de substituto.

Exemplo: migração implementada com um banco de dados pré-empacotado

Suponha que o app implemente um caminho de migração da versão 2 para a versão 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();

Veja o que acontece nesta situação:

  1. Como o banco de dados definido no app está na versão 3 e o banco de dados já instalado no dispositivo está na versão 2, é necessário fazer uma migração.
  2. Como há um caminho de migração implementado da versão 2 para a versão 3, o Room executa o método migrate() definido a fim de atualizar a instância do banco de dados no dispositivo para a versão 3, preservando os dados que já estão nesse banco. O Room não usa o arquivo de banco de dados pré-empacotado, porque ele usa esses arquivos somente no caso de uma migração de substituto.

Exemplo: migração em várias etapas com um banco de dados pré-empacotado

Os arquivos de banco de dados pré-empacotados também podem afetar migrações de várias etapas. Considere este caso:

  • Seu app define um banco de dados do Room na versão 4.
  • a instância do banco de dados instalada no dispositivo está na versão 2.
  • há um arquivo de banco de dados pré-empacotado que está na versão 3;
  • Há um caminho de migração implementado da versão 3 para a versão 4, mas não da versão 2 para a versão 3.
  • as migrações destrutivas estão ativadas.

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();

Veja o que acontece nesta situação:

  1. Como o banco de dados definido no app está na versão 4 e a instância do banco de dados já instalada no dispositivo está na versão 2, é necessário fazer uma migração.
  2. Como não há um caminho de migração implementado da versão 2 para a versão 3, trata-se de uma migração de substituto.
  3. Como o método de builder fallbackToDestructiveMigration() é chamado, a migração de substituto é destrutiva. O Room descarta a instância do banco de dados no dispositivo.
  4. Como há um arquivo do banco de dados pré-empacotado que está na versão 3, o Room recria o banco de dados e o preenche usando o conteúdo do arquivo pré-empacotado.
  5. O banco de dados instalado no dispositivo agora está na versão 3. Como essa versão ainda é anterior à versão definida no app, é necessário fazer outra migração.
  6. Como há um caminho de migração implementado da versão 3 para a versão 4, o Room executa o método migrate() definido a fim de atualizar a instância do banco de dados no dispositivo para a versão 4, preservando os dados copiados de banco de dados pré-empacotado da versão 3.

Outros recursos

Para saber mais sobre como preencher automaticamente um banco de dados do Room, consulte estes recursos.

Vídeos

Blogs