O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Como acessar dados usando DAOs da Room

Para acessar dados do seu app usando a biblioteca de persistência Room, aplique objetos de acesso a dados (DAOs, na sigla em inglês). Esse conjunto de objetos Dao forma o componente principal da Room, já que cada DAO inclui métodos que oferecem acesso abstrato ao banco de dados do app.

Ao acessar um banco de dados usando uma classe DAO em vez de builders de consultas ou consultas diretas, é possível separar diferentes componentes da arquitetura do banco de dados. Além disso, os DAOs permitem que você simule facilmente o acesso ao banco de dados ao testar seu app.

Um DAO pode ser uma interface ou uma classe abstrata. Se for uma classe abstrata, ele pode ter um construtor que tenha RoomDatabase como único parâmetro. A Room cria cada implementação de DAO no momento da compilação.

Observação: a Room não é compatível com o acesso a bancos de dados na linha de execução principal, a menos que allowMainThreadQueries() seja chamado no builder. Isso porque a IU pode ficar bloqueada por um longo período. Essa regra não se aplica a consultas assíncronas, ou seja, consultas que retornam instâncias de LiveData ou Flowable, porque elas executam a consulta de forma assíncrona em uma linha de execução em segundo plano quando necessário.

Definir métodos para conveniência

Há diversas consultas de conveniência que podem ser representadas usando uma classe DAO. Este documento inclui vários exemplos comuns.

Insert

Quando um método DAO é criado e anotado com @Insert, a Room gera uma implementação que insere todos os parâmetros no banco de dados em uma única transação.

O snippet de código a seguir mostra várias consultas de exemplo.

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);
    }
    

Se o método @Insert recebe apenas um parâmetro, ele pode retornar um long, que é o novo rowId para o item inserido. Se o parâmetro for uma matriz ou uma coleção, ele retornará long[] ou List<Long>.

Para ver mais detalhes, consulte a documentação de referência da anotação @Insert, bem como a documentação do SQLite para tabelas rowid (link em inglês).

Update

O método de conveniência Update modifica no banco de dados um conjunto de entidades fornecidas como parâmetros. Ele usa uma consulta que corresponde à chave primária de cada entidade.

O snippet de código a seguir demonstra como definir esse método.

Kotlin

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

Java

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

É possível fazer com que esse método retorne um valor int, indicando o número de linhas atualizadas no banco de dados, embora isso geralmente não seja necessário.

Delete

O método de conveniência Delete remove do banco de dados um conjunto de entidades fornecidas como parâmetros. Ele usa as chaves primárias para encontrar as entidades a serem excluídas.

O snippet de código a seguir demonstra como definir esse método.

Kotlin

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

Java

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

Você pode fazer com que esse método retorne um valor int, indicando o número de linhas removidas do banco de dados, embora isso geralmente não seja necessário.

Consultar informações

@Query é a principal anotação usada em classes DAO. Ela permite que você realize operações de leitura e gravação em um banco de dados. Cada método @Query é verificado no momento da compilação. Assim, caso haja um problema com a consulta, ocorrerá um erro de compilação em vez de uma falha do ambiente de execução.

A Room também verifica o valor de retorno da consulta de forma que, se o nome do campo no objeto retornado não for igual aos nomes de colunas correspondentes na resposta à consulta, a Room alertará você de uma das duas formas a seguir:

  • emitindo um alerta, se só alguns nomes de campos forem iguais;
  • exibindo um erro, se nenhum nome de campo for igual.

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

Essa é uma consulta muito simples que carrega todos os usuários. No momento de compilação, o Room sabe que todas as colunas na tabela do usuário estão sendo consultadas. Se a consulta tiver um erro de sintaxe ou se a tabela de usuário não existir no banco de dados, o Room exibirá um erro com a mensagem adequada, à medida que seu app for compilado.

Como transmitir parâmetros para a consulta

Na maioria das vezes, você precisa transmitir parâmetros a consultas para realizar operações de aplicação de filtros, por exemplo, exibir apenas usuários com idade acima da determinada. Para realizar essa tarefa, use parâmetros de método na sua anotação do Room, conforme mostrado no snippet de código a seguir.

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);
    }
    

Quando essa consulta é processada no momento da compilação, a Room faz com que o parâmetro de vinculação :minAge corresponda ao parâmetro do método minAge. A Room realiza a correspondência usando os nomes dos parâmetros. Se houver uma incompatibilidade, ocorrerá um erro quando o app for compilado.

Você também pode transmitir vários parâmetros ou se referenciar a eles várias vezes em uma consulta, conforme mostrado no snippet de código a seguir.

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);
    }
    

Como retornar subconjuntos de colunas

Na maioria das vezes, você precisa apenas de alguns campos de uma entidade. Por exemplo, sua IU pode exibir apenas o nome e o sobrenome do usuário, em vez de todos detalhes sobre ele. Realizando busca somente nas colunas que aparecem na IU do seu app, você economiza recursos valiosos, e sua consulta é concluída mais rapidamente.

O Room permite que você retorne qualquer objeto baseado em Java de suas consultas, desde que o conjunto de colunas de resultados possa ser mapeado para o objeto retornado. Por exemplo, você pode criar o seguinte objeto simples antigo do Java (POJO, na sigla em inglês) para buscar o nome e sobrenome do usuário:

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;
    }
    

Agora você pode usar esse POJO no seu método de consulta:

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

A Room entende que a consulta retorna valores para as colunas de first_name e last_name e que esses valores podem ser mapeados para os campos da classe NameTuple. Assim, a Room pode gerar o código adequado. Se a consulta retornar colunas demais ou uma coluna que não existe na classe NameTuple, a Room exibirá um alerta.

Como transmitir uma coleção de argumentos

Algumas das consultas podem exigir que você transmita um número variável de parâmetros, com o número exato de parâmetros não conhecidos até o tempo de execução. Por exemplo, talvez você queira recuperar informações sobre todos os usuários de um subconjunto de regiões. O Room entende quando um parâmetro representa uma coleção e o expande automaticamente no tempo de execução, com base no número de parâmetros fornecidos.

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);
    }
    

Acesso direto com cursor

Se a lógica do seu app exige acesso direto às linhas de retorno, é possível retornar um objeto Cursor das suas consultas, conforme mostrado no snippet de código a seguir.

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);
    }
    

Cuidado: não é recomendável trabalhar com a API Cursor, porque ela não garante que as linhas existam ou quais valores estão contidos nelas. Só use essa função se você já tiver um código que precisa de cursor e que não pode ser refatorado facilmente.

Como consultar várias tabelas

Algumas consultas podem exigir acesso a várias tabelas para calcular o resultado. A Room permite que você crie qualquer consulta. Portanto, você também pode mesclar tabelas. Além disso, se a resposta for um tipo de dado observável, por exemplo, Flowable ou LiveData, a Room verificará se há invalidação de todas as tabelas referenciadas na consulta.

O snippet de código a seguir mostra como mesclar tabelas para consolidar informações entre uma tabela que contém usuários que estão pegando livros emprestados e outra que contém dados sobre os livros atualmente emprestados.

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);
    }
    

Você também pode retornar POJOs dessas consultas. Por exemplo, você pode criar uma consulta que carregue o nome de um usuário e do seu animal de estimação da seguinte forma:

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 retorno de consulta

A Room é compatível com vários tipos de retorno para métodos de consulta, incluindo tipos de retorno especializados para interoperabilidade com frameworks ou APIs específicos. A tabela a seguir mostra os tipos de retorno aplicáveis com base no tipo de consulta e no framework:

Tipo de consulta Corrotinas RxJava Guava Lifecycle
Leitura observável Flow<T> Flowable<T>, Publisher<T>, Observable<T> N/A LiveData<T>
Leitura única suspend fun Single<T>, Maybe<T> ListenableFuture<T> N/A
Gravação única suspend fun Single<T>, Maybe<T>, Completable<T> ListenableFuture<T> N/A

Consultas reativas com Flow

Na Room 2.2 e versões mais recentes, é possível usar a função Flow do Kotlin (link em inglês) para garantir que a IU do app permaneça atualizada. Para que a IU seja atualizada automaticamente quando os dados subjacentes mudarem, programe métodos de consulta que retornem objetos Flow:

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

Sempre que qualquer um dos dados na tabela muda, o objeto Flow retornado aciona a consulta novamente e emite mais uma vez todo o conjunto de resultados.

As consultas reativas que usam Flow têm uma limitação importante: o objeto Flow realiza a consulta sempre que uma linha da tabela é atualizada, esteja ela no conjunto de resultados ou não. Aplique o operador distinctUntilChanged() (link em inglês) ao objeto Flow retornado para garantir que a IU só será notificada quando os resultados reais da consulta mudarem:

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

Consultas assíncronas com corrotinas de Kotlin

Você pode adicionar a palavra-chave suspend de Kotlin aos métodos DAO para torná-los assíncronos usando a função de corrotinas de Kotlin. Isso garante que eles não possam ser executados na linha de execução 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>
    }
    

Essa orientação também se aplica aos métodos DAO anotados com @Transaction. É possível usar esse recurso para criar métodos de suspensão de bancos de dados a partir de outros métodos DAO. Esses métodos serão executados em uma única transação do banco de dados.

@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 saber mais sobre o uso de corrotinas de Kotlin no seu app, consulte Melhorar o desempenho do app com corrotinas de Kotlin.

Consultas observáveis com LiveData

Ao realizar consultas, geralmente é recomendável que a IU do app seja atualizada automaticamente quando os dados forem modificados. Para isso, use um valor de retorno do tipo LiveData na descrição do método de consulta. A Room gera todo o código necessário para atualizar o LiveData quando o banco de dados é atualizado.

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);
    }
    

Consultas reativas com RxJava

A Room oferece a seguinte compatibilidade com valores de retorno dos tipos RxJava2:

Para usar essa função, inclua a versão mais recente do artefato rxjava2 no arquivo build.gradle do app:

app/build.gradle

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

Para ver as versões atuais dessa biblioteca, consulte as informações sobre a Room na página de versões.

O snippet de código a seguir mostra vários exemplos de como é possível usar esses tipos de retorno.

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 ver mais detalhes, consulte o artigo Room e RxJava do Google Developers (link em inglês).

Outros recursos

Para saber mais sobre como acessar dados usando DAOs da Room, consulte os outros recursos a seguir.

Amostras

Codelabs

  • Android Room com um View (Java e Kotlin, links em inglês)

Blogs