Indo um pouco além com Room

Indo um pouco além com Room
Matheus Brandino
Matheus Brandino

Compartilhe

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.

Banner promocional da Alura, com um design futurista em tons de azul, apresentando dois blocos de texto, no qual o bloco esquerdo tem os dizeres:

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.

Matheus Brandino
Matheus Brandino

Matheus é desenvolvedor com foco em Java e mobile (Android), formado em Sistemas de Informação.

Veja outros artigos sobre Mobile