Preencher automaticamente o banco de dados do Room

Se você quiser que seu app comece com um banco de dados já carregado com um conjunto específico de dados, preencha o banco de dados. No Room, você pode usar APIs para preencher um banco de dados na inicialização com conteúdo de um arquivo de banco de dados pré-empacotado no sistema de arquivos do dispositivo.

Pré-preencher a partir de um recurso de link para 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/ do app, chame a função createFromAsset do objeto RoomDatabase.Builder antes de chamar build:

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

A função 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.

Pré-preencher automaticamente a partir do 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 a função createFromFile do objeto RoomDatabase.Builder antes de chamar build:

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

A função createFromFile aceita um File argumento 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 preencherá o banco de dados recém-recriado com o conteúdo do arquivo de banco de dados pré-empacotado após a execução da migração destrutiva.

Para mais informações sobre as migrações de banco de dados do Room, consulte Migrar seu banco 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 já 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.

// 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()
}

Veja o que acontece nessa 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 você chama a função de criação fallbackToDestructiveMigrationbuilder, 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 usando o conteúdo do arquivo pré-empacotado. Se o arquivo de banco de dados pré-empacotado estiver na versão 2, o Room determinará que ele não corresponde à versão de destino e não o usará para a 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:

// 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()
}

Veja o que acontece nessa 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 a função migrate definida 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 já 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.

// 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()
}

Veja o que acontece nessa 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, a migração é uma migração de substituto.
  3. Como você chama a função de criação fallbackToDestructiveMigrationbuilder, 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 usando o conteúdo do arquivo pré-empacotado.
  5. O banco de dados instalado no dispositivo agora está na versão 3. Como ainda é menor que a versão definida no app, outra migração é necessária.
  6. Como há um caminho de migração implementado da versão 3 para a versão 4, o Room executa a função migrate definida 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.