7 consejos útiles para usar la habitación

7 consejos útiles para usar la habitación


Room es una capa de abstracción sobre SQLite que simplifica el almacenamiento. Si es nuevo en Room, consulte este artículo introductorio:


7 pasos para usar la habitación. Tutorial para migrar su aplicación a Room

Y en este artículo me gustaría compartir algunos consejos sobre cómo usar Room de la manera más eficiente posible.


1. Rellenar previamente la base de datos


¿Necesita agregar datos predeterminados a su base de datos inmediatamente después de su creación o en el momento del primer acceso a ella? Utilice RoomDatabase # Callback . Llame al método addCallback al crear su base de datos y anule onCreate u onOpen .


onCreate a onCreate cuando se onCreate la base de datos por primera vez, inmediatamente después de crear las tablas. Se llama a onOpen cuando se abre la base de datos. Dado que el acceso al DAO es posible solo después de completar estos métodos, creamos una nueva secuencia en la que obtenemos un enlace a la base de datos, luego obtenemos el DAO e insertamos los datos necesarios.


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

Vea el ejemplo completo aquí .


Nota: si utiliza el enfoque ioThread y la aplicación se bloquea la primera vez que inicia, entre la creación de la base de datos y la inserción, los datos nunca se insertarán.


2. Uso de las capacidades de herencia DAO


¿Tiene varias tablas en su base de datos y copia los mismos métodos de inserción , actualización y eliminación ? Los DAO admiten herencia, por lo tanto, cree una BaseDao<T> y defina sus métodos comunes @Insert , @Update y @Delete . Deje que cada DAO extienda BaseDao y agregue métodos específicos para cada uno de ellos.


 interface BaseDao<T> { @Insert fun insert(vararg obj: T) } @Dao abstract class DataDao : BaseDao<Data>() { @Query("SELECT * FROM Data") abstract fun getData(): List<Data> } 

Ver detalles aquí .


Los DAO deben ser interfaces o clases abstractas porque Room genera su implementación en tiempo de compilación, incluidos los métodos de BaseDao .


3. Ejecución de consultas en transacciones sin código repetitivo


Anotar un método usando @Transaction asegura que todas las operaciones de base de datos que realice en este método se ejecuten dentro de la misma transacción. No se ejecutará una transacción si se produce una excepción en el cuerpo del 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() } 

Es posible que desee utilizar la anotación @Transaction para los métodos @Transaction que utilizan la instrucción select en casos:


  • Cuando el resultado de la consulta es bastante grande. Al realizar una solicitud en una transacción, usted garantiza que si el resultado de la solicitud no cabe en una "porción" del cursor, no se dañará debido a cambios en la base de datos entre permutaciones del cursor .
  • Cuando el resultado de la consulta es un POJO con campos @Relation . Cada campo es una solicitud en sí misma, por lo que ejecutarlos en una transacción garantiza resultados consistentes entre las solicitudes.

Los @Delete , @Update y @Insert , que tienen varios parámetros, se @Insert automáticamente dentro de la transacción.


4. Leer solo lo que necesitas


Cuando realiza una solicitud a la base de datos, ¿utiliza todos los campos que obtiene en la respuesta? Cuide la cantidad de memoria utilizada por su aplicación y descargue solo los campos que finalmente utilizará. También aumentará la velocidad de sus solicitudes al reducir el costo de E / S. Room hará el mapeo entre las columnas y el objeto por usted.


Considere este objeto de User complejo:


 @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) 

En algunas pantallas no necesitamos mostrar toda esta información. Por lo tanto, en su lugar, podemos crear un objeto UserMinimal que contenga solo los datos necesarios.


 data class UserMinimal(val userId: String, val firstName: String, val lastName: String) 

En la clase DAO, definimos la consulta y seleccionamos las columnas correctas de la tabla de users .


 @Dao interface UserDao { @Query(“SELECT userId, firstName, lastName FROM Users) fun getUsersMinimal(): List<UserMinimal> } 

5. Control de dependencias entre entidades con claves foráneas.


Aunque Room no admite directamente relaciones entre entidades, le permite definir dependencias entre objetos utilizando claves foráneas.


Room tiene la anotación @ForeignKey , que forma parte de la anotación @Entity . En funcionalidad, es similar a las claves foráneas en SQLite . Garantiza la preservación de las relaciones entre entidades cuando se realizan cambios en la base de datos. Para agregarlo, defina el objeto al que se hará referencia, así como las columnas del objeto actual y el volumen al que se refiere.


Considere la clase User y Pet . Pet tiene un propietario: el ID de usuario al que hace referencia la clave externa.


 @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) 

Si lo desea, puede determinar qué acción tomar cuando el padre se elimina o actualiza en la base de datos. Puede elegir una de las siguientes opciones: NO_ACTION , RESTRICT , SET_NULL , SET_DEFAULT o CASCADE , que se comportan igual que en SQLite .


Nota: en la sala, SET_DEFAULT funciona como SET_NULL , porque Room aún no le permite establecer valores predeterminados para las columnas.


6. Simplifique las consultas de uno a muchos con @Relation


En el ejemplo anterior User - Pet , podemos decir que existe una relación de uno a muchos : el usuario puede tener varias mascotas. Supongamos que queremos obtener una lista de usuarios con sus mascotas: List<UserAndAllPets> .


 data class UserAndAllPets (val user: User, val pets: List<Pet> = ArrayList()) 

Para hacer esto manualmente, necesitamos 2 consultas: una para obtener una lista de todos los usuarios y la otra para obtener una lista de mascotas basada en la identificación del usuario.


 @Query(“SELECT * FROM Users”) public List<User> getUsers(); @Query(“SELECT * FROM Pets where owner = :userId”) public List<Pet> getPetsForUser(String userId); 

Luego iteraremos sobre la lista de usuarios y accederemos a la tabla Pets cada vez.


La anotación @Relation facilitará nuestra vida: solicitará automáticamente objetos relacionados. @Relation solo se puede aplicar a List o Set . Actualice la clase UserAndAllPets:


 class UserAndAllPets { @Embedded var user: User? = null @Relation(parentColumn = “userId”, entityColumn = “owner”) var pets: List<Pet> = ArrayList() } 

En el DAO, definimos una consulta, y Room consultará las tablas de Users y Pets y mapeará los objetos por sí mismo.


 @Transaction @Query(“SELECT * FROM Users”) List<UserAndAllPets> getUsers(); 

7. Evitar notificaciones falsas para solicitudes observables


Supongamos que desea obtener un usuario por su identificador utilizando una solicitud observable:


 @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> 

Recibirá un nuevo objeto User cada vez que se actualice. Pero también recibirá este objeto cuando se produzcan otros cambios en la tabla Users (eliminaciones, actualizaciones o inserciones) que no tienen nada que ver con el usuario que le interesa, lo que dará lugar a notificaciones falsas. Además, si su consulta incluye varias tablas, recibirá nuevos mensajes cada vez que algo cambie en alguna de ellas.


Esto es lo que sucede detrás de escena:


  1. SQLite tiene activadores que se activan cada vez que DELETE , UPDATE o INSERT ocurre en una tabla.
  2. Room crea un InvalidationTracker que utiliza Observers que rastrean todos los cambios en las tablas observadas.
  3. Las solicitudes LiveData y LiveData basan en la notificación InvalidationTracker.Observer # onInvalidated . Cuando se recibe, se produce una solicitud repetida.

Room solo sabe que la mesa ha cambiado, pero no sabe por qué y qué ha cambiado. Por lo tanto, después de una segunda solicitud, el resultado de la solicitud se transmite usando LiveData o LiveData . Porque Room no almacena ningún dato en la memoria; no puede determinar si es el mismo dato o no.


Debe asegurarse de que su DAO filtre las solicitudes y responda solo a los objetos necesarios.


Si la consulta observable se implementa 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() } 

Si su consulta devuelve LiveData , puede usar MediatorLiveData , que recibirá solo los objetos deseados de la fuente.


 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 } 

En sus DAO, el método que devuelve LiveData es public y el método que consulta la base de datos está 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() } 

Vea el ejemplo de código completo aquí .


Nota: si solicita una lista para mostrar, preste atención a la Biblioteca de paginación , que devolverá un LivePagedListBuilder . La biblioteca lo ayudará a calcular automáticamente la diferencia entre los elementos de la lista y actualizar su interfaz de usuario.


Vea también: 7 pasos para usar Room. Tutorial para migrar su aplicación a Room

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


All Articles