Introdução à persistência de dados com Room

in #android7 years ago

room.png

Todo desenvolvedor Android sabe como é complicado trabalhar com SQLite no Android, boilerplate para todos os lados, códigos acoplados e SQLs enormes, tudo isso gera uma enorme perda de produtividade e bugs que assustariam até o Frankenstein. Pensando nisto, desenvolvedores optam por sacrificar a performance da aplicação em troca de uma produtividade melhor, utilizando ORMs já conhecidos no mercado ou o Realm, mas e se existisse uma outra alternativa?

Em 2017 durante o Google I/O, foram lançadas um conjunto de bibliotecas para o Android, o Android Architecture Componentes. Entre elas, uma me chamou bastante atenção... Uma biblioteca para persistência de dados chamada Room!

Room nada mais é que uma biblioteca para mapeamento de objetos SQLite em Java que cria uma camada de abstração sobre o SQLite, tornando desnecessária a escrita de boa parte do SQL, eliminando os problemas existentes ao usar o SQLite padrão do Android.

Existem 3 componentes para se construir uma aplicação com Room: Entity, DAO e Database.

  • Entity representa os dados de uma tabela, referenciada por uma annotation em uma classe de dados.
  • DAO define os métodos de acesso ao banco, usando annotations para vincular o SQL com o método.
  • Database é a classe que contém referências dos DAOs, é o meio de acesso principal para a conexão com o BD.
    roomstep.png

Parece simples... Mas é!
Como exemplo de implementação, vamos utilizar uma aplicação que está em meu Github

Mãos na massa

  1. Primeiro, adicione as dependências em seu build.gradle.

    implementation "android.arch.persistence.room:runtime:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
    
  2. Agora crie uma classe Java chamada Person, está será a Entity, classe que representará a tabela Person no BD.

    @Entity
    public class Person {
    
        @PrimaryKey(autoGenerate = true)
        private long id;
        @ColumnInfo(name = "first_name")
        private String firstName;
        @ColumnInfo(name = "last_name")
        private String lastName;
        private int age;
        private String email;
    
        public Person() {}
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }
    

    Toda Entity deve conter a annotation @Entity, note também que há outras annotations. @PrimaryKey indica que o atributo id deve ser uma primary key, @ColumnInfo define propriedades específicas da coluna, como o nome dela por exemplo, esta annotation não é obrigatória, Room automaticamente cria a coluna com o nome do atributo, caso queira que ele crie com outro nome, então use a annotation @ColumnInfo com a propriedade name.

  3. Crie uma interface chamada PersonDao com a annotation @Dao

    @Dao
    public interface PersonDao {
    
        @Insert
        long insert(Person person);
    
        @Insert
        void insertAll(List<Person> personList);
    
        @Update
        int update(Person person);
    
        @Delete
        void delete(Person person);
    
        @Query("SELECT * FROM person")
        List<Person> getAll();
    
        @Query("SELECT * FROM person WHERE id = :idperson")
        Person getById(int idperson);
    
    }
    

    Room utiliza 4 annotations no DAO, @Insert para inserir dados na tabela, @Update para atualizar dados na tabela, @Delete para remover dados e @Query para executar SQL, note que há dois métodos com @Insert, isto é para demonstrar que é possível fazer inserção de um único objeto e também de uma lista de objetos.

  4. Crie uma classe chamada AppDatabase que estende a RoomDatabase, a classe deve conter a annotation @Database

    @Database(entities = {Person.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
    
        private static AppDatabase INSTANCE;
    
        public abstract PersonDao personDao();
    
        public static AppDatabase getAppDatabase(Context context) {
            if(INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "mydb")
                    //.fallbackToDestructiveMigration()
                    //.allowMainThreadQueries()
                    .build();
            }
    
            return INSTANCE;
        }
    
        public static void destroyInstance() {
            INSTANCE = null;
        }
    }
    

    A classe AppDatabase terá a tarefa de gerenciar o BD, contendo a versão do banco (declarado na propriedade version da annotation @Database), as Entitys (declarado na propriedade entity da annotation @Database), todos os DAOs, migrations (exemplos em um novo artigo!) e outras propriedades do banco. Note que criei a classe como um singleton, porém, não é obrigatório, fiz um singleton para não ter necessidade de instanciar a classe sempre que for usar o BD.

    Deixei algumas opções comentadas, .fallbackToDestructiveMigration() removerá os dados do BD sempre que houver uma alteração na versão, isso facilitará seus testes durante o desenvolvimento, pois o Room faz cache dos dados. .allowMainThreadQueries() permite que o acesso ao banco seja executado na Main Thread, use esta opção apenas caso queira fazer alguns testes e está com preguiça de criar threads, NÃO USE ISTO EM PRODUÇÃO OU UMA TERRÍVEL MALDIÇÃO CAIRÁ SOBRE SUA APLICAÇÃO (vulgo ANR).

  5. Tudo pronto, agora faça o teste, tente inserir um objeto Person!

    final Person person = new Person();
    person.setFirstName("João");
    person.setLastName("Das Neves");
    person.setAge(30);
    person.setEmail([email protected]);
    
        new Thread(new Runnable() {
            @Override
            public void run() {
                AppDatabase db = AppDatabase.getAppDatabase(getBaseContext());
    
                db.personDao().insert(person);
                finish();
            }
        }).start();
    

Conclusão

Room é uma tentativa do Google de tornar o desenvolvimento de aplicações Android mais produtivo e menos suscetivo a erros, eliminando a necessidade de usar ORMs pesados ou dar preferência ao SQLite nativo. Erros são gerados durante a compilação (escreveu SQL errado? Não compila!), há um padrão organizacional com annotations e interfaces, há integração com LiveData (próximo artigo?!) e rxJava e também há total integração com o SQLite nativo do Android, dando possibilidade de migrar seu banco nativo aos poucos.

Google recomenda que se utilize o Room, inclusive, para substituir a maneira antiga de se gerenciar um banco SQLite.

Nos vemos no meu próximo artigo!