7 dicas úteis para usar o quarto

7 dicas úteis para usar o quarto


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) // moving to a new thread ioThread { getInstance(context).dataDao() .insert(PREPOPULATE_DATA) } } }) .build() 

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> // or @Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): Flowable<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:


  1. O SQLite possui gatilhos que disparam sempre que DELETE , UPDATE ou INSERT ocorre em uma tabela.
  2. Room cria um InvalidationTracker que usa Observers que rastreiam todas as alterações nas tabelas observadas.
  3. 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>() { /** * Get a user by id. * @return the user from the table with a specific id. */ @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

Source: https://habr.com/ru/post/pt442786/


All Articles