Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo acceder a los datos con DAO de Room

Para acceder a los datos de tu app con la biblioteca de persistencias Room, trabaja con objetos de acceso a datos o DAO. Este conjunto de objetos Dao forma el componente principal de Room, ya que cada DAO incluye métodos que ofrecen acceso abstracta a la base de datos de tu app.

Cuando se accede a una base de datos con una clase DAO, en lugar de compiladores de consultas o consultas directas, puedes separar diferentes componentes de la arquitectura de tu base de datos. Además, las clases DAO te permiten simular fácilmente el acceso a la base de datos mientras pruebas tu app.

Una clase DAO puede ser una interfaz o una clase abstracta. Si es una clase abstracta, puede tener opcionalmente un constructor que tome un RoomDatabase como su único parámetro. Room crea cada implementación de DAO en el momento de la compilación.

Nota: Room no admite el acceso a la base de datos en el subproceso principal, a menos que hayas llamado a allowMainThreadQueries() en el compilador porque podría bloquear la IU durante un período prolongado. Las consultas asíncronas (que muestran instancias de LiveData o Flowable) están exentas de esta regla porque ejecutan la consulta de forma asíncrona cuando es necesario.

Cómo definir métodos para mayor comodidad

Hay múltiples consultas de conveniencia que puedes representar mediante una clase DAO. En este documento, se incluyen varios ejemplos comunes.

Cómo insertar

Cuando creas un método DAO y lo anotas con @Insert, Room genera una implementación que inserta todos los parámetros en la base de datos en una sola transacción.

En el siguiente fragmento de código, se muestran varias consultas de ejemplo:

Kotlin

    @Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insertUsers(vararg users: User)

        @Insert
        fun insertBothUsers(user1: User, user2: User)

        @Insert
        fun insertUsersAndFriends(user: User, friends: List<User>)
    }
    

Java

    @Dao
    public interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        public void insertUsers(User... users);

        @Insert
        public void insertBothUsers(User user1, User user2);

        @Insert
        public void insertUsersAndFriends(User user, List<User> friends);
    }
    

Si el método @Insert recibe solo 1 parámetro, puede mostrar un long, que es el nuevo rowId para el elemento insertado. Si el parámetro es un arreglo o una colección, debería mostrar long[] o List<Long>.

Para obtener más detalles, consulta la documentación de referencia de la anotación @Insert y la documentación de SQLite para las tablas rowid.

Cómo actualizar

El método de conveniencia Update modifica un conjunto de entidades, proporcionadas como parámetros, en la base de datos. Utiliza una consulta que coincide con la clave primaria de cada entidad.

En el siguiente fragmento de código, se muestra cómo definir este método:

Kotlin

    @Dao
    interface MyDao {
        @Update
        fun updateUsers(vararg users: User)
    }
    

Java

    @Dao
    public interface MyDao {
        @Update
        public void updateUsers(User... users);
    }
    

Aunque, si bien no suele ser necesario, puedes hacer que este método muestre un valor int, que indica la cantidad de filas actualizadas en la base de datos.

Cómo borrar

El método de conveniencia Delete quita un conjunto de entidades, proporcionadas como parámetros, de la base de datos. Utiliza las claves primarias para encontrar las entidades que se borrarán.

En el siguiente fragmento de código, se muestra cómo definir este método:

Kotlin

    @Dao
    interface MyDao {
        @Delete
        fun deleteUsers(vararg users: User)
    }
    

Java

    @Dao
    public interface MyDao {
        @Delete
        public void deleteUsers(User... users);
    }
    

Aunque, si bien no suele ser necesario, puedes hacer que este método muestre un valor int, que indica la cantidad de filas que se quitaron de la base de datos.

Cómo consultar información

@Query es la anotación principal que se usa en las clases DAO. Te permite realizar operaciones de lectura/escritura en una base de datos. Cada método @Query se verifica en el momento de la compilación, por lo que si hay un problema con la consulta, se produce un error de compilación en lugar de una falla del entorno de ejecución.

Room también verifica el valor que se muestra de la consulta, de modo que si el nombre del campo del objeto que se muestra no coincide con los nombres de las columnas correspondientes en la respuesta de la consulta, Room te alerta de una de estas dos maneras:

  • Muestra una advertencia si solo coinciden algunos nombres de campos.
  • Muestra un error si no coinciden los nombres de los campos.

Consultas simples

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user")
        fun loadAllUsers(): Array<User>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user")
        public User[] loadAllUsers();
    }
    

Esta es una consulta muy simple que carga todos los usuarios. En el momento de compilación, Room sabe que está consultando todas las columnas de la tabla de usuarios. Si la consulta contiene un error de sintaxis o si la tabla de usuarios no existe en la base de datos, Room muestra un error con el mensaje apropiado a medida que se compila tu app.

Cómo pasar parámetros a la consulta

La mayoría de las veces, debes pasar parámetros a consultas con el objetivo de realizar operaciones de filtrado, como mostrar solo usuarios mayores de cierta edad. Para llevar a cabo esta tarea, usa los parámetros del método en tu anotación Room, como se muestra en el siguiente fragmento de código:

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        fun loadAllUsersOlderThan(minAge: Int): Array<User>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        public User[] loadAllUsersOlderThan(int minAge);
    }
    

Cuando se procesa esta consulta en el momento de la compilación, Room hace coincidir el parámetro de vinculación :minAge con el parámetro del método minAge. Room realiza la coincidencia con los nombres de los parámetros. Si hay una falta de coincidencia, se produce un error al compilar tu app.

También puedes pasar varios parámetros o hacer referencia a ellos varias veces en una consulta, como se muestra en el siguiente fragmento de código:

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        fun findUserWithName(search: String): List<User>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        public List<User> findUserWithName(String search);
    }
    

Cómo mostrar subconjuntos de columnas

En la mayoría de los casos, solo necesitas obtener algunos campos de una entidad. Por ejemplo, tu IU puede mostrar solo el nombre y el apellido de un usuario, en lugar de mostrar todos sus detalles. Gracias a que obtienes solo las columnas que aparecen en la IU de tu app, ahorras recursos valiosos y la consulta se completa más rápido.

Room te permite mostrar cualquier objeto basado en Java de tus consultas siempre que el conjunto de columnas de resultados se pueda asignar al objeto que se muestra. Por ejemplo, puedes crear el siguiente objeto antiguo y sin formato basado en Java (POJO) para obtener el nombre y apellido del usuario:

Kotlin

    data class NameTuple(
        @ColumnInfo(name = "first_name") val firstName: String?,
        @ColumnInfo(name = "last_name") val lastName: String?
    )
    

Java

    public class NameTuple {
        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        @NonNull
        public String lastName;
    }
    

Ahora, puedes usar este POJO en tu método de consulta de la siguiente manera:

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        fun loadFullName(): List<NameTuple>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }
    

Room comprende que la consulta muestra valores para las columnas first_name y last_name, y que se pueden asignar esos valores a los campos de la clase NameTuple. Por lo tanto, Room puede generar el código correcto. Si la consulta muestra demasiadas columnas o una columna que no existe en la clase NameTuple, Room muestra una advertencia.

Cómo pasar una colección de argumentos

Es posible que algunas de las consultas requieran que se pase una cantidad variable de parámetros y que no se conozca la cantidad exacta de parámetros hasta el tiempo de ejecución. Por ejemplo, es posible que quieras recuperar información sobre todos los usuarios de un subconjunto de regiones. Room entiende cuándo un parámetro representa una colección y lo expande automáticamente en el tiempo de ejecución en función de la cantidad de parámetros proporcionados.

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        fun loadUsersFromRegions(regions: List<String>): List<NameTuple>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public List<NameTuple> loadUsersFromRegions(List<String> regions);
    }
    

Cómo acceder directamente al cursor

Si la lógica de tu app requiere acceso directo a las filas mostradas, puedes mostrar un objeto Cursor de tus consultas, como se indica en el siguiente fragmento de código:

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        fun loadRawUsersOlderThan(minAge: Int): Cursor
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        public Cursor loadRawUsersOlderThan(int minAge);
    }
    

Precaución: No se recomienda trabajar con la API del cursor porque no garantiza que las filas existen o qué valores contienen. Usa esta funcionalidad solo si ya tienes un código que espera un cursor y que no se puede refactorizar fácilmente.

Cómo hacer consultas en varias tablas

Algunas de tus consultas pueden requerir acceso a varias tablas para calcular el resultado. Room te permite escribir cualquier consulta, por lo que también puedes unir tablas. Además, si la respuesta es un tipo de dato observable, como Flowable o LiveData, Room mira todas las tablas a las que se hace referencia en la consulta para la invalidación.

En el siguiente fragmento de código, se muestra cómo realizar una combinación de tablas para consolidar información entre una tabla que contiene usuarios que toman prestados libros y una tabla que contiene datos sobre libros prestados actualmente:

Kotlin

    @Dao
    interface MyDao {
        @Query(
            "SELECT * FROM book " +
            "INNER JOIN loan ON loan.book_id = book.id " +
            "INNER JOIN user ON user.id = loan.user_id " +
            "WHERE user.name LIKE :userName"
        )
        fun findBooksBorrowedByNameSync(userName: String): List<Book>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM book " +
               "INNER JOIN loan ON loan.book_id = book.id " +
               "INNER JOIN user ON user.id = loan.user_id " +
               "WHERE user.name LIKE :userName")
       public List<Book> findBooksBorrowedByNameSync(String userName);
    }
    

También puedes mostrar objetos POJO desde estas consultas. Por ejemplo, puedes escribir una consulta que cargue a un usuario y el nombre de su mascota de la siguiente manera:

Kotlin

    @Dao
    interface MyDao {
        @Query(
            "SELECT user.name AS userName, pet.name AS petName " +
            "FROM user, pet " +
            "WHERE user.id = pet.user_id"
        )
        fun loadUserAndPetNames(): LiveData<List<UserPet>>

        // You can also define this class in a separate file.
        data class UserPet(val userName: String?, val petName: String?)
    }
    

Java

    @Dao
    public interface MyDao {
       @Query("SELECT user.name AS userName, pet.name AS petName " +
              "FROM user, pet " +
              "WHERE user.id = pet.user_id")
       public LiveData<List<UserPet>> loadUserAndPetNames();

       // You can also define this class in a separate file, as long as you add the
       // "public" access modifier.
       static class UserPet {
           public String userName;
           public String petName;
       }
    }
    

Tipos de datos que se muestran de consultas

Room admite una variedad de tipos de datos que se muestra para los métodos de consulta, incluidos los tipos de datos que se muestran especializados para la interoperabilidad con API o frameworks específicos. En la siguiente tabla, se muestran los tipos de devolución aplicables según el tipo de consulta y el framework:

Tipo de consulta Corrutinas RxJava Guava Ciclo de vida
Lectura observable Flow<T> Flowable<T>, Publisher<T>, Observable<T> N/D LiveData<T>
Lectura de una toma suspend fun Single<T>, Maybe<T> ListenableFuture<T> N/D
Escritura por única vez suspend fun Single<T>, Maybe<T>, Completable<T> ListenableFuture<T> N/D

Cómo hacer consultas reactivas con Flow

En Room 2.2 y versiones posteriores, puedes asegurarte de que la IU de tu app se mantenga actualizada mediante la funcionalidad Flow de Kotlin. Para que se actualice automáticamente la IU cuando cambien los datos subyacentes, escribe métodos de consulta que muestren objetos Flow:

@Query("SELECT * FROM User")
    fun getAllUsers(): Flow<List<User>>
    

Cada vez que cambia alguno de los datos de la tabla, el objeto Flow que se muestra vuelve a activar la consulta y vuelve a emitir el conjunto de resultados completo.

Las consultas reactivas que usan Flow tienen una limitación importante: el objeto Flow vuelve a ejecutar la consulta cada vez que se actualiza una fila de la tabla, independientemente de si está en el conjunto de resultados. Puedes asegurarte de que solo se notifique a la IU cuando cambien los resultados de la consulta mediante la aplicación del operador distinctUntilChanged() al objeto Flow mostrado:

@Dao
    abstract class UsersDao {
        @Query("SELECT * FROM User WHERE username = :username")
        abstract fun getUser(username: String): Flow<User>

        fun getUserDistinctUntilChanged(username:String) =
               getUser(username).distinctUntilChanged()
    }
    

Cómo hacer consultas asíncronas con corrutinas de Kotlin

Puedes agregar la palabra clave suspend Kotlin a tus métodos DAO para que sean asíncronos mediante la funcionalidad de corrutinas de Kotlin. De esta manera, te aseguras de que no se puedan ejecutar en el subproceso principal.

@Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        suspend fun insertUsers(vararg users: User)

        @Update
        suspend fun updateUsers(vararg users: User)

        @Delete
        suspend fun deleteUsers(vararg users: User)

        @Query("SELECT * FROM user")
        suspend fun loadAllUsers(): Array<User>
    }
    

Esta guía también se aplica a los métodos DAO anotados con @Transaction. Puedes usar esta función para compilar métodos de bases de datos de suspensión a partir de otros métodos DAO. Estos métodos se ejecutan en una sola transacción de base de datos.

@Dao
    abstract class UsersDao {
        @Transaction
        open suspend fun setLoggedInUser(loggedInUser: User) {
            deleteUser(loggedInUser)
            insertUser(loggedInUser)
        }

        @Query("DELETE FROM users")
        abstract fun deleteUser(user: User)

        @Insert
        abstract suspend fun insertUser(user: User)
    }
    

Para obtener más información sobre el uso de las corrutinas de Kotlin en tu app, consulta Cómo mejorar el rendimiento de la app con corrutinas de Kotlin.

Cómo hacer consultas observables con LiveData

Cuando realices consultas, a menudo desearás que se actualice automáticamente la IU de tu app cuando cambien los datos. Para lograrlo, usa un valor de retorno de tipo LiveData en la descripción del método de consulta. Room genera todo el código necesario para actualizar el LiveData cuando se actualiza la base de datos.

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
    }
    

Cómo hacer consultas reactivas con RxJava

Room proporciona la siguiente compatibilidad con valores de muestra de tipos RxJava2:

Para usar esta funcionalidad, incluye la versión más reciente del artefacto rxjava2 en el archivo build.gradle de tu app:

app/build.gradle

    dependencies {
        def room_version = "2.1.0"
        implementation 'androidx.room:room-rxjava2:$room_version'
    }
    

Para ver las versiones actuales de esta biblioteca, consulta la información sobre Room en la página de versiones.

En el siguiente fragmento de código, se muestran varios ejemplos de cómo podrías usar estos tipos de datos que se muestran:

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        fun loadUserById(id: Int): Flowable<User>

        // Emits the number of users added to the database.
        @Insert
        fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int>

        // Makes sure that the operation finishes successfully.
        @Insert
        fun insertLargeNumberOfUsers(varargs users: User): Completable

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        fun deleteAllUsers(users: List<User>): Single<Int>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        public Flowable<User> loadUserById(int id);

        // Emits the number of users added to the database.
        @Insert
        public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

        // Makes sure that the operation finishes successfully.
        @Insert
        public Completable insertLargeNumberOfUsers(User... users);

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        public Single<Integer> deleteUsers(List<User> users);
    }
    

Para obtener información detallada, consulta el artículo de Google Developers Room y RxJava.

Recursos adicionales

Para obtener más información sobre cómo acceder a datos mediante DAO de sala, consulta los siguientes recursos adicionales.

Ejemplos

Codelabs

Blogs