
Room é uma biblioteca que faz parte dos componentes arquitetônicos do Android. Facilita o trabalho com objetos SQLiteDatabase
no aplicativo, reduzindo a quantidade de código padrão e verificando as consultas SQL em tempo de compilação.
Já tem um projeto Android que usa SQLite para armazenar dados? Nesse caso, você pode migrá-lo para o quarto. Vamos ver como pegar um projeto existente e refatorá-lo para usar o Room em 7 etapas simples.
TL; DR: atualize as dependências do gradle, crie suas entidades, DAO e banco de dados, substitua chamadas SQLiteDatabase
chamadas de método DAO, teste tudo o que você criou ou modificou e exclua classes não utilizadas. Isso é tudo!
Em nosso aplicativo de amostra para migração, trabalhamos com objetos do tipo User
. Usamos tipos de produtos para demonstrar várias implementações no nível de dados:
- sqlite - usa
SQLiteOpenHelper
e interfaces tradicionais SQLite. - room - substitui a implementação por Room e fornece migração.
Cada opção usa a mesma camada da interface do usuário, que funciona com a classe UserRepository
graças ao padrão MVP.
Na variante sqlite, você verá muitos códigos duplicados e que usam o banco de dados nas LocalUserDataSource
e LocalUserDataSource
. As consultas são criadas usando ContentValues
, e os dados retornados pelos objetos Cursor
são lidos coluna por coluna. Todo esse código contribui para o aparecimento de erros implícitos. Por exemplo, você pode pular a adição de uma coluna a uma consulta ou montar incorretamente um objeto a partir de um banco de dados.
Vamos ver como o Room aprimora nosso código. Inicialmente, simplesmente copiamos as classes da variante sqlite e as mudamos gradualmente.
Etapa 1. Atualizando as Dependências de Nivelamento
Dependências para quarto estão disponíveis no novo repositório do Google Maven. Basta adicioná-lo à lista de repositórios no seu arquivo build.gradle
principal:
allprojects { repositories { google() jcenter() } }
Defina a versão da biblioteca da sala no mesmo arquivo. Enquanto estiver na versão alfa, fique atento às atualizações de versão nas páginas do desenvolvedor :
ext { roomVersion = '2.1.0-alpha03' }
No app/build.gradle
adicione dependências para o Room:
dependencies { implementation “android.arch.persistence.room:runtime:$rootProject.roomVersion” annotationProcessor “android.arch.persistence.room:compiler:$rootProject.roomVersion” androidTestImplementation “android.arch.persistence.room:testing:$rootProject.roomVersion” }
Para migrar para o Room, precisamos aumentar a versão do banco de dados e, para salvar os dados do usuário, precisamos implementar a classe Migration . Para testar a migração , precisamos exportar o esquema. Para fazer isso, adicione o seguinte código ao app/build.gradle
:
android { defaultConfig { ... // used by Room, to test migrations javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } } // used by Room, to test migrations sourceSets { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } ...
Etapa 2. Atualizando Classes de Modelo para Entidades
Sala cria uma tabela para cada classe marcada como @Entity . Os campos na classe correspondem às colunas na tabela. Consequentemente, as classes de entidade, como regra, são pequenas classes de modelos que não contêm nenhuma lógica. Nossa classe User
representa um modelo para dados em um banco de dados. Então, vamos atualizá-lo para informar ao Room que ele deve criar uma tabela com base nesta classe:
- Anote a classe com
@Entity
e use a propriedade tableName
para definir o nome da tabela. - Defina a chave primária adicionando a anotação
@PrimaryKey
aos campos corretos - no nosso caso, esse é o ID do usuário. - Especifique o nome da coluna para os campos da classe usando a anotação
@ColumnInfo(name = "column_name")
. Você pode pular esta etapa se seus campos já tiverem o nome da coluna. - Se houver vários construtores na classe, adicione a anotação
@Ignore
para informar à Sala qual usar e qual não usar.
@Entity(tableName = "users") public class User { @PrimaryKey @ColumnInfo(name = "userid") private String mId; @ColumnInfo(name = "username") private String mUserName; @ColumnInfo(name = "last_update") private Date mDate; @Ignore public User(String userName) { mId = UUID.randomUUID().toString(); mUserName = userName; mDate = new Date(System.currentTimeMillis()); } public User(String id, String userName, Date date) { this.mId = id; this.mUserName = userName; this.mDate = date; } ... }
Nota: para uma migração suave, preste muita atenção aos nomes de tabelas e colunas na implementação original e certifique-se de configurá-los corretamente nas @ColumnInfo
e @ColumnInfo
.
Etapa 3. Criando Objetos de Acesso a Dados (DAO)
Os DAOs são responsáveis por definir métodos de acesso ao banco de dados. Na implementação inicial do nosso projeto SQLite, todas as consultas ao banco de dados foram executadas na classe LocalUserDataSource
, onde trabalhamos com objetos Cursor
. No Room, não precisamos de todo o código associado ao cursor e podemos simplesmente definir nossas solicitações usando anotações na classe UserDao
.
Por exemplo, ao consultar todos os usuários do banco de dados, o Room faz todo o "trabalho duro" e precisamos apenas escrever:
@Query(“SELECT * FROM Users”) List<User> getUsers();
Etapa 4. Criando um Banco de Dados
Já definimos nossa tabela Users
e suas consultas correspondentes, mas ainda não criamos um banco de dados que unirá todos esses componentes do Room. Para fazer isso, precisamos definir uma classe abstrata que estenda o RoomDatabase
. Essa classe está marcada como @Database
, que lista os objetos contidos no banco de dados e o DAO que os acessa. A versão do banco de dados deve ser aumentada em 1 em comparação com o valor original, portanto, no nosso caso, será 2.
@Database(entities = {User.class}, version = 2) @TypeConverters(DateConverter.class) public abstract class UsersDatabase extends RoomDatabase { private static UsersDatabase INSTANCE; public abstract UserDao userDao();
Como queremos salvar os dados do usuário, precisamos implementar a classe Migration
informando ao Room o que ele deve fazer ao mudar da versão 1 para 2. No nosso caso, como o esquema do banco de dados não mudou, simplesmente fornecemos uma implementação vazia:
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) {
Crie um objeto de banco de dados na classe UsersDatabase
, definindo o nome e a migração do banco de dados:
database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabase.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build();
Para saber mais sobre como implementar a migração de banco de dados e como eles funcionam, confira esta postagem .
Etapa 5. Atualizando o repositório para usar o Room
Criamos nosso banco de dados, nossa tabela de usuários e consultas, agora é hora de usá-los. Neste ponto, atualizaremos a classe LocalUserDataSource
para usar os métodos UserDao
. Para fazer isso, primeiro atualizamos o construtor: remova o Context
e adicione UserDao
. Obviamente, qualquer classe que crie uma instância de LocalUserDataSource
também deve ser atualizada.
Em seguida, atualizaremos os métodos LocalUserDataSource
que fazem consultas chamando os métodos UserDao
. Por exemplo, um método que solicita todos os usuários agora se parece com isso:
public List<User> getUsers() { return mUserDao.getUsers(); }
E agora é a hora de lançar o que fizemos.
Um dos melhores recursos do Room é que, se você executar operações de banco de dados no thread principal, seu aplicativo falhará com a seguinte mensagem de erro:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Uma maneira confiável de mover operações de E / S do encadeamento principal é criar um novo Runnable
que criará um novo encadeamento para cada consulta ao banco de dados. Como já usamos essa abordagem na variante sqlite, nenhuma alteração foi necessária.
Etapa 6. Testando no dispositivo
Criamos novas classes - UserDao
e UsersDatabase
e alteramos nosso LocalUserDataSource
para usar o banco de dados Room. Agora precisamos testá-los.
Testando o UserDao
Para testar o UserDao
, precisamos criar uma classe de teste AndroidJUnit4
. O recurso impressionante do Room é a capacidade de criar um banco de dados na memória. Isso elimina a necessidade de limpeza após cada teste.
@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); }
Também precisamos garantir que fechemos a conexão com o banco de dados após cada teste.
@After public void closeDb() throws Exception { mDatabase.close(); }
Por exemplo, para testar o login de um usuário, adicionamos um usuário e, em seguida, verificamos se conseguimos obter esse usuário do banco de dados.
@Test public void insertAndGetUser() {
Testando o uso do UserDao no LocalUserDataSource
É fácil garantir que LocalUserDataSource
ainda esteja funcionando corretamente, pois já temos testes que descrevem o comportamento dessa classe. Tudo o que precisamos fazer é criar um banco de dados na memória, obter um objeto UserDao
e usá-lo como parâmetro para o construtor LocalUserDataSource
.
@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); mDataSource = new LocalUserDataSource(mDatabase.userDao()); }
Novamente, precisamos garantir que fechemos o banco de dados após cada teste.
Teste de Migração de Banco de Dados
Você pode ler mais sobre como implementar testes de migração de banco de dados, além de como o MigrationTestHelper
funciona, nesta postagem .
Você também pode ver o código em um exemplo de aplicativo de migração mais detalhado.
Etapa 7. Removendo todos os desnecessários
Remova todas as classes e linhas de código não utilizadas que agora são substituídas pela funcionalidade da sala. Em nosso projeto, precisamos apenas remover a classe UsersDbHelper
, que estendeu a classe SQLiteOpenHelper
.
Se você possui um banco de dados maior e mais complexo e deseja mudar gradualmente para o Room, recomendamos esta postagem .
Agora, a quantidade de código padrão propenso a erros diminuiu, as solicitações são verificadas no momento da compilação e tudo é testado. Em 7 etapas simples, conseguimos migrar nosso aplicativo existente para o Room. Você pode ver um exemplo de aplicativo aqui .