Indo um pouco além com Room
Na criação de aplicativos quantas vezes caímos na necessidade de manter dados salvos para economizar o pacote de dados dos usuários ? Acredito que a resposta deve ser bem próxima de "muitas vezes" ou até mesmo "sempre".
A parte mais chata é que toda vez que queremos manter um cache de nossa aplicação precisamos criar uma classe que extenda de SQLiteOpenHelper
. Aí que os problemas começam, quando eu tenho mais de uma tabela, como eu faço?
Precisaria criar um monte de DAOs que tenha como atributo a classe que faz o acesso direto ao banco. Isso seria um grande trabalho, além de ter muito código repetido pra lá e prá cá, o que a galera geralmente chama de codigo boilerplate.
Pra solucionar esse problema a galera do google criou uma serie de bibliotecas que fazem parte de um conjunto de ferramentas denominadas Architecture Components. Dentro dele podemos encontrar um ORM
, cujo nome é Room.
Já falei bastante dele no meu curso aqui na Alura, mas ficamos bem na base dele lá, vimos como ele facilita e muito o desenvolvimento.
Agora estou querendo evoluir um pouco da conversa. Lá fiz um sistema com apenas duas entitidades, Prova
e Aluno
, bem similar a isso aqui:
@Entity public class Prova implements Serializable {
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "prova_id") private Long id;
private String materia; private Calendar data; // getters // setters } @Entity public class Aluno implements Serializable {
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "aluno_id") private Long id;
private String nome; private String email; private Calendar nascimento;
// getters // setters }
É bem comum pensarmos agora que uma nova tarefa seria implementar o relacionamento de ambas as entidades, até mesmo, porque um aluno realiza uma prova. Essa junção pra gente, representa o Resultado
e como parece ser bem importante isso para nosso sistema, seria legal gerar uma nova entidade para ele:
@Entity public class Resultado {
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "resultadoId") private Long id;
private Prova prova;
private Aluno aluno;
private Double nota; //getters //setters }
Ai é quando caímos numa das pequenas ciladas que o mundo android nos dá. Estamos tão habituados a fazer esse tipo de relação no mundo web que não pensamos duas vezes fazes antes de efetivamente transcrever isso para esse mundo.
A biblioteca não consegue fazer esse "inferimento automático". Precisamos deixar claro pra ela que existe uma relação entre as tabelas. Primeiro precisamos mudar o tipo dos objetos, mapeando exatamente como será o banco de dados em si:
@Entity public class Resultado {
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "resultadoId") private Long id;
private int provaId;
private int alunoId;
private Double nota; //getters //setters }
Com isso o Room já vai ficar feliz por saber como precisa criar a tabela, contudo ainda não falamos que ambos ids são chaves estrangeiras. Para isso, precisamos colocar uma anotação que vai deixar claro e criar todas as constraints para nós, contudo fazemos isso na declaração da entidade:
@Entity(foreignKeys = { @ForeignKey(entity = Aluno.class, parentColumns = "aluno_id", childColumns = "alunoId"), @ForeignKey(entity = Prova.class, parentColumns = "prova_id", childColumns = "provaId") } ) public class Resultado { }
Dessa forma o Room já criou nossa tabela, mas há outro detalhe que é legal ficarmos de olho: se alguma prova ou algum aluno for removido da base, ficaremos com dados totalmente inconsistentes. Para resolver esse problema, podemos adicionar uma condição exclua junto o resultado, essa ação é conhecida como ação em cascata:
@Entity(foreignKeys = { @ForeignKey(entity = Aluno.class, parentColumns = "aluno_id", childColumns = "alunoId", onDelete = ForeignKey.CASCADE), @ForeignKey(entity = Prova.class, parentColumns = "prova_id", childColumns = "provaId", onDelete = ForeignKey.CASCADE) } ) public class Resultado { }
Agora basta criarmos nosso DAO para manipular nossos resultados. Mas, se formos parar novamente para pensar, quando formos fazer uma busca no banco e usar os resultados, como vou saber quem é o aluno com o id 1 por exemplo?
Acho que vai ser bem difícil isso. Trazer o aluno com suas informações, é nessa parte que precisamos fazer uma busca um pouco mais inteligente. Já pensando que precisamos trazer os dados tanto da prova quanto do aluno junto com o resultado.
Uma query com joins já resolve esse problema, mas e agora? Como fazemos para representar isso no nosso sistema?
A solução proposta pelo Room é que a criação de um objeto que represente exatamente a busca que estamos fazendo, algo similar a isso:
public class ResultadoComDados {
private Prova prova;
private Aluno aluno;
private Resultado resultado; // getters // setters }
Com isso, podemos montar nossa query da seguinte forma:
@Query("select \* from resultado join prova on prova_id = provaId join aluno on aluno_id = alunoId") List<ResultadoComDados> listar();
Contudo, quando executarmos o código e pegarmos essa lista, vamos ver que não terá nada! Esquecemos um detalhe bem importante: toda vezes que tivermos mapeando um objeto dessa maneira, precisamos deixar claro para o Room que dentro do resultado haverão nossos objetos. Fazemos isso através da anotação @Embedded
public class ResultadoComDados {
@Embedded private Prova prova;
@Embedded private Aluno aluno;
@Embedded private Resultado resultado; // getters // setters
}
Rodando agora nosso aplicativo teremos o resultado que estavámos buscando!
E ai gostou de saber um pouco mais sobre o Room? Não conhecia ainda, corre lá e faz meu curso de Room na Alura :D.
Nele você vai ver como automatizar a criação do seu banco sem muitos problemas, fazer um crud de maneira simples e objetiva e fazer queries dinâmicas, além disso vai aprender diversas boas práticas de programação mobile.