The Android Developer Challenge is back! Submit your idea before December 2.

Salvar dados em um banco de dados local usando o Room

O banco de dados Room oferece uma camada de abstração sobre o SQLite para permitir acesso fluente ao banco de dados e, ao mesmo tempo, aproveitar toda a capacidade do SQLite.

Apps que processam quantidades não triviais de dados estruturados podem se beneficiar muito da persistência desses dados localmente. O caso de uso mais comum é armazenar as partes de dados relevantes em cache. Dessa forma, quando o dispositivo não conseguir acessar a rede, o usuário ainda poderá navegar pelo conteúdo enquanto estiver off-line. Todas as alterações de conteúdo feitas pelo usuário serão sincronizadas com o servidor quando o dispositivo ficar on-line novamente.

É altamente recomendável usar o Room em vez do SQLite, porque o Room cuida desses problemas para você. No entanto, se você prefere usar APIs SQLite diretamente, leia Salvar dados usando o SQLite.

Existem três componentes principais no Room:

  • Banco de dados: contém o suporte do banco de dados e serve como o principal ponto de acesso para a conexão com dados relacionais e persistentes do app.

    A classe que está anotada com @Database precisa atender às seguintes condições:

    • ser uma classe abstrata que estende RoomDatabase;
    • incluir a lista de entidades associadas ao banco de dados na anotação;
    • conter um método abstrato que tenha 0 argumentos e retorne a classe que é anotada com @Dao.

    É possível adquirir uma instância de Database chamando Room.databaseBuilder() ou Room.inMemoryDatabaseBuilder() durante a execução.

  • Entidade: representa uma tabela dentro do banco de dados.

  • DAO: contém os métodos utilizados para acessar o banco de dados.

O app usa o banco de dados do Room para conseguir os objetos de acesso a dados (DAOs, na sigla em inglês) associados a esse banco de dados. Em seguida, o app usa cada DAO para conseguir entidades do banco de dados e salvar as alterações dessas entidades de volta no banco de dados. Por fim, o app usa uma entidade para conseguir e definir valores que correspondam a colunas da tabela no banco de dados.

Essa relação entre os diferentes componentes do Room aparece na Figura 1:

Figura 1. Diagrama da arquitetura do Room.

O snippet de código a seguir contém uma configuração de banco de dados de exemplo, com uma entidade e um DAO.

User

Kotlin

    @Entity
    data class User(
        @PrimaryKey val uid: Int,
        @ColumnInfo(name = "first_name") val firstName: String?,
        @ColumnInfo(name = "last_name") val lastName: String?
    )
    

Java

    @Entity
    public class User {
        @PrimaryKey
        public int uid;

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

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

UserDao

Kotlin

    @Dao
    interface UserDao {
        @Query("SELECT * FROM user")
        fun getAll(): List<User>

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        fun loadAllByIds(userIds: IntArray): List<User>

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        fun findByName(first: String, last: String): User

        @Insert
        fun insertAll(vararg users: User)

        @Delete
        fun delete(user: User)
    }
    

Java

    @Dao
    public interface UserDao {
        @Query("SELECT * FROM user")
        List<User> getAll();

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        List<User> loadAllByIds(int[] userIds);

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);

        @Insert
        void insertAll(User... users);

        @Delete
        void delete(User user);
    }
    

AppDatabase

Kotlin

    @Database(entities = arrayOf(User::class), version = 1)
    abstract class AppDatabase : RoomDatabase() {
        abstract fun userDao(): UserDao
    }
    

Java

    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }
    

Após criar os arquivos acima, é possível conseguir uma instância do banco de dados criado usando o seguinte código:

Kotlin

    val db = Room.databaseBuilder(
                applicationContext,
                AppDatabase::class.java, "database-name"
            ).build()
    

Java

    AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "database-name").build();
    

Observação: se seu app é executado em um único processo, siga o padrão de projeto singleton ao instanciar um objeto AppDatabase. Cada instância RoomDatabase é bastante cara e raramente é necessário ter acesso a várias instâncias em um único processo.

Se seu app é executado em vários processos, inclua enableMultiInstanceInvalidation() na invocação do criador do banco de dados. Dessa forma, quando você tiver uma instância de AppDatabase em cada processo, será possível invalidar o arquivo do banco de dados compartilhado em um processo. Essa invalidação será automaticamente propagada para as instâncias de AppDatabase em outros processos.

Para ter uma experiência prática com o Room, veja os codelabs Android Room com visualização e Persistência Android (links em inglês). Para navegar por exemplos de código do Room, veja os exemplos de Componentes da Arquitetura do Android (link em inglês).