
Room é uma camada de abstração em cima do SQLite que simplifica o armazenamento. Se você é novo no Room, confira este artigo introdutório:
7 etapas para usar o Room. Passo a passo para migrar seu aplicativo para o Room
E neste artigo, gostaria de compartilhar algumas dicas sobre como usar o Room da maneira mais eficiente possível.
1. Pré-preenchendo o Banco de Dados
Você precisa adicionar dados padrão ao seu banco de dados imediatamente após sua criação ou no momento do primeiro acesso a ele? Use RoomDatabase # Callback . Chame o método addCallback ao criar seu banco de dados e substitua onCreate ou onOpen .
onCreate
será chamado quando o banco de dados for criado, imediatamente após a criação das tabelas. onOpen
é chamado quando o banco de dados é aberto. Como o acesso ao DAO é possível somente após a conclusão desses métodos, criamos um novo fluxo no qual obtemos um link para o banco de dados, obtemos o DAO e inserimos os dados necessários.
Room.databaseBuilder(context.applicationContext, DataDatabase::class.java, "Sample.db") // prepopulate the database after onCreate was called .addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db)
Veja o exemplo completo aqui .
Nota: se você usar a abordagem ioThread
e o aplicativo travar na primeira vez em que iniciar, entre a criação do banco de dados e a inserção, os dados nunca serão inseridos.
2. Usando recursos de herança do DAO
Você possui várias tabelas no seu banco de dados e copia os mesmos métodos de inserção , atualização e exclusão ? Os DAOs suportam herança, portanto, crie uma BaseDao<T>
e defina seus métodos comuns @Insert
, @Update
e @Delete
. Deixe cada DAO estender o BaseDao
e adicionar métodos específicos para cada um deles.
interface BaseDao<T> { @Insert fun insert(vararg obj: T) } @Dao abstract class DataDao : BaseDao<Data>() { @Query("SELECT * FROM Data") abstract fun getData(): List<Data> }
Veja detalhes aqui .
Os DAOs devem ser interfaces ou classes abstratas porque o Room gera sua implementação em tempo de compilação, incluindo métodos do BaseDao
.
3. Executando consultas em transações sem código clichê
Anotar um método usando @Transaction garante que todas as operações do banco de dados que você executa neste método sejam executadas na mesma transação. Uma transação não será executada se ocorrer uma exceção no corpo do método.
@Dao abstract class UserDao { @Transaction open fun updateData(users: List<User>) { deleteAllUsers() insertAll(users) } @Insert abstract fun insertAll(users: List<User>) @Query("DELETE FROM Users") abstract fun deleteAllUsers() }
Convém usar a anotação @Transaction
para métodos @Transaction
que usam a instrução select
nos casos:
- Quando o resultado da consulta é bastante grande. Ao fazer uma solicitação em uma transação, você garante que, se o resultado da solicitação não couber em uma "parte" do cursor, ele não será danificado devido a alterações no banco de dados entre as permutações do cursor .
- Quando o resultado da consulta é um POJO com campos
@Relation
. Cada campo é uma solicitação em si, portanto, executá-los em uma transação garante resultados consistentes entre as solicitações.
Os @Delete
, @Update
e @Insert
, que possuem vários parâmetros, são iniciados automaticamente dentro da transação.
4. Lendo apenas o que você precisa
Ao fazer uma solicitação ao banco de dados, você usa todos os campos que obtém na resposta? Cuide da quantidade de memória usada pelo seu aplicativo e faça o download apenas dos campos que você usará. Também aumentará a velocidade de suas solicitações, reduzindo o custo de E / S. O quarto fará o mapeamento entre as colunas e o objeto para você.
Considere este objeto de User
complexo:
@Entity(tableName = "users") data class User(@PrimaryKey val id: String, val userName: String, val firstName: String, val lastName: String, val email: String, val dateOfBirth: Date, val registrationDate: Date)
Em algumas telas, não precisamos exibir todas essas informações. Assim, em vez disso, podemos criar um objeto UserMinimal
que contém apenas os dados necessários.
data class UserMinimal(val userId: String, val firstName: String, val lastName: String)
Na classe DAO, definimos a consulta e selecionamos as colunas corretas da tabela de users
.
@Dao interface UserDao { @Query(“SELECT userId, firstName, lastName FROM Users) fun getUsersMinimal(): List<UserMinimal> }
5. Controle de dependências entre entidades com chaves estrangeiras
Mesmo que o Room não suporte diretamente relacionamentos entre entidades, ele permite definir dependências entre objetos usando chaves estrangeiras.
A sala possui a anotação @ForeignKey , que faz parte da anotação @Entity . Na funcionalidade, é semelhante às chaves estrangeiras no SQLite . Garante a preservação dos relacionamentos entre as entidades quando são feitas alterações no banco de dados. Para adicioná-lo, defina o objeto a ser referenciado, bem como as colunas no objeto atual e o volume ao qual você está se referindo.
Considere a classe User
e Pet
. Pet
possui um proprietário - o ID do usuário referenciado pela chave estrangeira.
@Entity(tableName = "pets", foreignKeys = arrayOf( ForeignKey(entity = User::class, parentColumns = arrayOf("userId"), childColumns = arrayOf("owner")))) data class Pet(@PrimaryKey val petId: String, val name: String, val owner: String)
Se desejar, você pode determinar que ação tomar quando o pai for excluído ou atualizado no banco de dados. Você pode escolher uma das seguintes opções: NO_ACTION
, RESTRICT
, SET_NULL
, SET_DEFAULT
ou CASCADE
, que se comportam da mesma forma que no SQLite .
Nota: na sala, SET_DEFAULT
funciona como SET_NULL
, porque A sala ainda não permite definir valores padrão para colunas.
6. Simplifique as consultas um para muitos com o @Relation
No exemplo anterior User
- Pet
, podemos dizer que existe um relacionamento um para muitos : o usuário pode ter vários animais de estimação. Suponha que desejemos obter uma lista de usuários com seus animais de estimação: List<UserAndAllPets>
.
data class UserAndAllPets (val user: User, val pets: List<Pet> = ArrayList())
Para fazer isso manualmente, precisamos de 2 consultas: uma para obter uma lista de todos os usuários e a outra para obter uma lista de animais de estimação com base no ID do usuário.
@Query(“SELECT * FROM Users”) public List<User> getUsers(); @Query(“SELECT * FROM Pets where owner = :userId”) public List<Pet> getPetsForUser(String userId);
Em seguida, percorreremos a lista de usuários e acessaremos a tabela Pets
sempre.
A anotação @Relation facilitará nossa vida: ela solicitará automaticamente objetos relacionados. @Relation
só pode ser aplicada a List
ou Set
. Atualize a classe UserAndAllPets:
class UserAndAllPets { @Embedded var user: User? = null @Relation(parentColumn = “userId”, entityColumn = “owner”) var pets: List<Pet> = ArrayList() }
No DAO, definimos uma consulta e o Room consultará as tabelas Users
e Pets
e mapeará os objetos por conta própria.
@Transaction @Query(“SELECT * FROM Users”) List<UserAndAllPets> getUsers();
7. Evitar notificações falsas para solicitações observáveis
Suponha que você queira obter um usuário por seu identificador usando uma solicitação observável:
@Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): LiveData<User>
Você receberá um novo objeto User
cada vez que for atualizado. Mas você também receberá esse objeto quando outras alterações ocorrerem na tabela Users
(exclusões, atualizações ou inserções) que nada têm a ver com o usuário em que você está interessado, o que levará a notificações falsas. Além disso, se sua consulta incluir várias tabelas, você receberá novas mensagens sempre que algo mudar em alguma delas.
Aqui está o que acontece nos bastidores:
- O SQLite possui gatilhos que disparam sempre que
DELETE
, UPDATE
ou INSERT
ocorre em uma tabela. - Room cria um InvalidationTracker que usa
Observers
que rastreiam todas as alterações nas tabelas observadas. - As solicitações
LiveData
e LiveData
contam com a notificação InvalidationTracker.Observer # onInvalidated . Quando é recebido, ocorre uma solicitação repetida.
A sala sabe apenas que a tabela foi alterada, mas não sabe por que e o que mudou. Portanto, após uma segunda solicitação, o resultado da solicitação é transmitido usando LiveData
ou LiveData
. Porque O quarto não armazena nenhum dado na memória, não pode determinar se são os mesmos dados ou não.
Você deve verificar se o seu DAO filtra solicitações e responde apenas aos objetos necessários.
Se a consulta observável for implementada usando Flowables
, use Flowable # diverUntilChanged .
@Dao abstract class UserDao : BaseDao<User>() { @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): Flowable<User> fun getDistinctUserById(id: String): Flowable<User> = getUserById(id) .distinctUntilChanged() }
Se sua consulta retornar o LiveData
, você poderá usar o MediatorLiveData
, que receberá apenas os objetos desejados da origem.
fun <T> LiveData<T>.getDistinct(): LiveData<T> { val distinctLiveData = MediatorLiveData<T>() distinctLiveData.addSource(this, object : Observer<T> { private var initialized = false private var lastObj: T? = null override fun onChanged(obj: T?) { if (!initialized) { initialized = true lastObj = obj distinctLiveData.postValue(lastObj) } else if ((obj == null && lastObj != null) || obj != lastObj) { lastObj = obj distinctLiveData.postValue(lastObj) } } }) return distinctLiveData }
Nos seus DAOs, o método que retorna o LiveData
é public
e o método que consulta o banco de dados é protected
.
@Dao abstract class UserDao : BaseDao<User>() { @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): LiveData<User> fun getDistinctUserById(id: String): LiveData<User> = getUserById(id).getDistinct() }
Veja o exemplo de código completo aqui .
Nota: se você estiver solicitando uma lista para exibição, preste atenção na Paging Library , que retornará um LivePagedListBuilder . A biblioteca ajudará você a calcular automaticamente a diferença entre os itens da lista e atualizar sua interface com o usuário.
Consulte também: 7 etapas para usar o Room. Passo a passo para migrar seu aplicativo para o Room