Introdução ao Banco de Dados Room e LiveData — Android Jetpack

Alifyz Pires
8 min readJan 20, 2019

--

O artigo de hoje será sobre Room a nova biblioteca do Android Jetpack para implementar banco de dados relacionais em aplicativos nativos — Android.

Room funciona como uma camada de abstração para o SQLite e graças aos recursos incríveis do Room como verificações de sintaxe em tempo de compilação, podemos evitar a escrita de códigos defeituosos ao implementar nossas operações do Banco de Dados — CRUD.

Além disso, podemos combinar o Room com LiveData e ViewModel para implementar um padrão observador (Observable Pattern) e facilitar o fluxo do aplicativo sempre que ele depender de alguma informação armazenada em um Banco de Dados, que de forma imprevisível pode sofrer alterações. Dessa forma, não precisamos fazer uma nova consulta sempre que a informação mudar, podemos deixar o LiveData nós avisar sempre que algo foi alterado no Banco.

Importante: Para simplificar este artigo, não iremos implementar o padrão Repository (Repositório), que é um padrão de arquitetura recomendada pela própria Google ao usar Room e LiveData.

Introdução ao Room (Google I/O)

Introdução ao projeto

Neste artigo, iremos implementar um banco de dados relacional para gerenciar anotações. Para tal, iremos refatorar um projeto existente para ganhar tempo e focarmos no conteúdo principal.

Veja como o aplicativo está atualmente

Código Inicial (Download)

Para melhor aproveitamento, é imprescindível você tenha familiaridade com os seguintes itens:

  • Conhecimento Intermediário de desenvolvimento de aplicativos Android.
  • Conhecimento de Banco de Dados SQLite
  • Criação de Listas com RecyclerView
  • Conhecimento do padrão Singleton

Introdução aos componentes do Room

Para implementar o Room precisamos construir três componentes principais, são eles:

  • RoomDatabase-> Banco de Dados
  • Entity -> Tabelas do Banco de Dados
  • DAO (Data Access Objects) -> Objetos para acessar e consultar as informações do Banco de Dados.
Arquitetura do Room.

Para utilizar a biblioteca adicione as seguintes dependências no gradle a nível de app.

def room_version = "1.1.1"

implementation "android.arch.persistence.room:runtime:$room_version"
kapt"android.arch.persistence.room:compiler:$room_version"
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:livedata:$lifecycle_version"
def anko_version='0.10.8'
implementation "org.jetbrains.anko:anko:$anko_version"

Criando as entidades (Tabelas)

O primeiro componente que iremos desenvolver será nossas Tabelas, que seria equivalente ao componente Entity do Room.

Representação em forma de Tabela da nossa entidade.

Para facilitar o processo de desenvolvimento e representação dos dados, Room utiliza um processador de anotações para gerar códigos de forma dinâmica, e com isso ganhamos a flexibilidade de escrever códigos mais limpos e deixamos a responsabilidade pela geração do boiler-plate para a biblioteca Room.

Para representar nossas anotações precisamos de duas informações: Texto principal, e a informação da prioridade. Para construção da entidade (Tabela) iremos utilizar uma classe que irá representar nossa anotação no banco de dados.

Crie uma nova classe chamada NotesEntity dentro do projeto inicial que você baixou no início do artigo.

Veja como a nossa entidade ficou:

As anotações, @Entity, @PrimaryKey, @ColumnInfo ajudam o Room a identificar pontos no código para geração dinâmica da implementação do banco de dados por debaixo dos panos.

Veja que com a anotação @Entity conseguimos alterar o nome da nossa tabela, e o mesmo vale para @ColumnInfo onde configuramos o nome da coluna na tabela para “texto”.

Como não informamos a anotação @ColumnInfo para o campo prioridade a biblioteca Room irá usar o nome da variável para setar o nome da coluna dentro da tabela.

Utilizamos uma data class no Kotlin para representar uma classe do tipo POJO, que possui a finalidade de representar informações em nosso código.

Implementar o DAO (Data Access Objects)

Agora que temos nossa entidade pronta, precisamos de uma maneira para acessar as informações do Banco de Dados. Para tal, o Room faz uso de um componente chamado DAO, que são objetos criados pela biblioteca Room que implementam operações CRUD para consulta e inserção de informações em nosso banco SQLite.

O principal objeto do DAO é prover uma comunicação com as informações armazenadas no banco de dados, possibilitando as operações de consulta, remoção e inserção de dados.

Para correta implementação devemos criar uma interface ou uma classe abstrata para criar nosso DAO, assim o próprio Room irá implementar o DAO de acordo com as anotações que especificarmos.

Como o nosso objetivo é bem simples, precisamos apenas de uma query para selecionar todos os registros do Banco de Dados e com tais informações exibi-lás na tela principal.

Veja como ficou nosso DAO.

Precisamos anotar a interface com @Dao para que Room possa identificar e implementar corretamente as demais anotações dentro da interface.

Em nosso DAO temos as seguintes operações:

  • @Query -> Utilizada para executar uma query para consulta de informações. Caso necessário, podemos executar uma @RawQuery com comandos instruções do SQLite customizadas.
  • @Insert -> Anotação usada para simbolizar uma operação de adição em nossa tabela, como argumento precisamos de um objeto do tipo Entity para que o Room possa armazenar em nossa tabela.
  • @Delete -> Operação usada para Deletar um registro do banco de dados. Precisamos passar um objeto que esteja armazenado em nosso banco de dados e assim o Room irá se encarregar de identificar o registro correto e deletar do banco de dados local.

Construindo o Banco de Dados

Por último, precisamos implementar a classe que irá representar e construir o banco de dados Room, para tal devemos criar uma classe abstrata para que o Room possa fazer a implementação do banco SQLite e assim não precisamos nos preocupar com a quantidade absurda de código necessário para implementar um simples banco de dados relacional.

A seguir, veja como ficou o código fonte do nosso banco:

Em nosso exemplo acima, anotamos a classe com @Database que precisa de dois argumentos chamados: entities e version.

Entities precisa necessariamente ser um array contento todas as entidades (tabelas do banco de dados), e por último version que especifica a versão do esquema do banco de dados.

Lembrando que sempre que alterarmos a estrutura do banco precisamos incrementar esse número, e em conjunto implementar uma operação de migração chamada databaseMigration, que não iremos abordar neste artigo.

Dentro da implementação de nossa classe NotesDatabase especificamos uma função que irá retornar um objeto que implementa a interface NotesDao para que possamos executar as operações descritas na interface em nosso banco de dados local.

Finalmente, dentro do escopo “companion object que seria o equivalente ao escopo estático do java, criamos um padrão de implementação chamado Singleton que é o responsável por criar a instância do banco de dados usando o método Rom.databaseBuilder para atribuir o objeto a nossa variável chamada INSTANCE e a retorna ao final do método.

E com isso temos os três componentes necessários para começar a usar o nosso banco de dados SQLite com Room.

Usando nosso Banco de Dados

Agora que temos o código necessário para usar o nosso banco de dados, precisamos codificar a lógica de nossa Activity principal para configurar nosso RecyclerView e também usar o banco de dados que especificamos na classe NotesDatabase.

Observe no código abaixo, temos duas variáveis globais:

  • database -> objeto que representa nosso banco de dados Room.
  • adapter -> objeto que representa nosso Adapter.

Repare que ao clicar no FloatingActionButton chamado fb_new_note somos redirecionados para outra Activity chamada NewNoteActivity, é nesta Activity que temos que codificar os métodos necessários para inserção de uma nova anotação no banco de dados.

Adicionando dados no Banco

Agora que temos o código da nossa Activity principal, precisamos codificar toda a lógica que irá pegar as informações da NewNoteActivity para inserção no banco de dados.

Pontos de atenção:

Para implementação do @Insert precisamos realizar essa operação em Background e fora da UI-Thread.

Para correta implementação dessa operação em outra Thread precisamos utilizar o Anko, uma biblioteca feita para facilitar o desenvolvimento Android em Kotlin.

Implementando LiveData

Agora que temos um aplicativo funcional, vamos implementar o LiveData para que ele possa nos avisar sempre que as informações de nosso Banco de Dados mudar. Dessa forma, não dependemos de acionar o menu “refresh” sempre que quisermos buscar a lista de anotações.

Esse padrão é conhecido como Observable Pattern.

Observable Pattern em nosso exemplo

A primeira coisa que precisamos fazer é modificar o nosso DAO para retornar objetos do tipo LiveData, veja abaixo:

Em seguida, vamos modificar a função onCreate da nossa activity principal:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

setupDatabase()
setupRecyclerView()

notesList.observe(this, Observer {
it
?.let { lista -> adapter.setData(lista) }
}
)

fab_new_note.setOnClickListener {
val newNoteActivity = Intent(this, NewNoteActivity::class.java)
startActivity(newNoteActivity)
}
}

Com o código acima, sempre que uma nova anotação for inserida no banco, ou algo mudar iremos recarregar a lista de anotações para o nosso adapter e sempre teremos a interface atualizada com o banco.

O código completo pode ser conferido abaixo.

O seguinte código é o responsável por transformar a MainActivity em um Observer:

notesList.observe(this, Observer {           
it?.let { lista -> adapter.setData(lista) }
})

Veja como está ficando nosso App:

Só tem um pequeno problema com o nosso aplicativo, no estado atual não é possível deletar nenhuma anotação do Banco.

Vamos implementar agora uma maneira de deletar os registros do banco.

Implementando Operações de Delete.

Vamos implementar a operação de delete ao dar Swipe na anotação da Lista. Para implementar uma operação ao dar Swipe precisamos de dois objetos. O primeiro deles é o ItemTouchHelper.Callback que executa os códigos de acordo com os eventos como: Swipe, Move, LongPress e demais.

Cada um dos eventos representa uma ação que o usuário pode executar no RecyclerView tais como, mover os itens de lugar, clique longo e etc.

A implementação do nosso ItemTouchHelper ficou conforme abaixo:

Agora só precisamos conectar o código acima com o RecyclerView na Activity principal.

Vamos alterar o método setupRecyclerView() da MainActivity. Para tal, confira a implementação em negrito.

//Configuração do Recyclerview
fun setupRecyclerView() {
val touchHelper = ItemTouchHelper(TouchHelper(adapter, this))
touchHelper.attachToRecyclerView(recyclerview)
recyclerview.layoutManager = LinearLayoutManager(this)
recyclerview.adapter = adapter
}

Agora, vamos modificar o layout de cada anotação para melhorar a interface de usuário. Vamos implementar um CardView com bordas arrendondadas e com uma margem visível entre cada uma das anotações.

Resultado final:

Configura o resultado final de nossa implementação:

Links, Recursos e Fontes:

Conclusão

Graças ao Room e ao Android Jetpack o processo de criação de um banco relacional ficou muito mais simples.

Em futuros artigos, iremos estender este projeto para implementação do padrão de arquitetura repositório que a própria Google recomenda na construção de aplicativos com o modelo Room, LiveData e ViewModel.

Qualquer dúvida, estou à disposição!

Mais artigos:

--

--

Alifyz Pires

Senior Software Engineer, Entrepreneur, Content Creator and other things!