Como usar a biblioteca Room no Android (Kotlin)?

Introdução ao Room

Observação: O repositório que contem o conteúdo desta aula é o Room-Kotlin.

O que é o Room

Room nada mais é do que uma biblioteca disponibilizada pela própria Google, por meio do Android Jetpack.

Que tem por objetivo nós ajudar a criar uma camada de abstração sobre o SQLite, para permitir acesso fluente ao banco de dados, de modo que conseguimos aproveitar toda a capacidade do banco de dados escrevendo o mínimo de código possível.

Isso faz com que a partir da Room, não precisamos mais acessar as classes e arquivos que trabalham diretamente com o banco de dados SQLitecomo visto neste repositório.

A diferença é que agora só vamos chamar a camada de abstração do Room, e com apenas poucos códigos conseguimos fazer consultas, inserções, atualizações e remoções no banco de dados (SQLite).

É importante ressaltar que o SQLite nada mais é do que um banco de dados que é instalado no próprio smartphone do usuário.

Vejamos abaixo alguns benefícios na utilização do Room:

  • Simplificação na execução do SQL,
  • Redução do código boilerplate (Código Padrão),
  • Consultas verificadas em tempo de compilação.

Estrutura do Room

O Room conta com 3 partes componentes, como é visto no diagrama abaixo:

Para que o Room funcione de fato, temos que criar todas as 3 camadas acima, as entites que representam as entidades da sua aplicação, a camada de abstração DAO, e por fim a declaração da classe do banco de dados.

Entites) É conhecida como a camada de entidades (os modelos de negócio da sua aplicação), e representa as tabelas presentes em nosso banco de dados.

Exemplo: Supondo que temos uma aplicação de venda de produtos, é bem provável que você tenha uma tabela no seu banco de dados, chamada de "Produtos", "Marcas", "Fornecedores", "Clientes" e afins.

Todas essas tabelas acima representam as entidades do seu banco de dados.

DAO) Significa "Objetos de Acesso a Dados", que nada mais são do que os nossos métodos que farão acesso ao banco de dados.

Quando você for fazer um Insert, Select, Update, Delete em suas tabelas, quem se responsabiliza por isso é a camada DAO.

Ou seja, é através da DAO que você executa uma QUERY, e ela te retorna o que você precisa.

Database) É a camada do banco de dados em si, que nada mais é do que um ponto de acesso ao banco.

Se trata de um arquivo que gera uma instância do banco de dados, ou cria o arquivo SQLite, isto é, caso for a primeira vez que você acessa o aplicativo.

Estrutura Inicial do Projeto

Com o Android Studio aberto, crie um novo projeto (File > New > New Project...).

Selecione a opção "Phone and Tablet" e escolha o bloco chamado "Empty Views Activity":

Clique em Next, e na próxima tela, informe o nome do seu aplicativo (eu nomeei como "Room"), e a versão mínima do SDK eu escolhi a versão 7.0.

Por fim, clique em finish para criar o projeto.

Implementação no Gradle

Para colocarmos a mão na massa, precisamos importar uma nova biblioteca no arquivo build.gradle (Module:app), existente na pasta Gradle Scripts:

Dentro de plugins, você vai precisar declarar um novo id chamado kotlin-kapt:

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}

Já dentro de dependencies, você vai precisar importar mais duas bibliotecas:

dependencies {
....

//Dependências do Room
def room_version = "2.5.0"

implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}

Observe que estamos trabalhando com a versão 2.5.0 que é a mais recente atualmente (26/08/2023), mas se preferir você pode buscar por uma versão mais nova, e implementar ali dentro, bastando apenas trocar aquela numeração definida dentro da variável chamada room_version.

Por fim, basta clicar em Sync Now para sincronizar, e clicar em "Build > Make Project" no canto superior do Android Studio para testar se esta tudo certo.

Possíveis erros de compilação

Durante esse processo, podem haver erros de compilação logo assim que você tentar fazer o build da sua aplicação.

O erro mais conhecido, e que aconteceu comigo foi este:

Execution failed for task ':app:kaptGenerateStubsDebugKotlin'.
> 'compileDebugJavaWithJavactask (current target is 1.8) and 'kaptGenerateStubsDebugKotlintask (current target is 17) jvm target compatibility should be set to the same Java version.
Consider using JVM toolchain: https://kotl.in/gradle/jvm/toolchain

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

Ele diz que a versão atual do Java que estou utilizando neste projeto é o 1.8, e a versão do Kapt está configurada para a versão 17.

Antes de continuar é importante que você saiba que o ambiente Java configurado na minha máquina do Windows é o Java da Versão Adoptium, configurada neste tutorial.

A minha versão do Java é a 17.0.7, e a versão do Android Studio que estou utilizando é o Flamingo 2022.2.1 Patch 2.

Com isso em mente, vamos corrigir este erro 

Antes de mais nada, tenha em mente que este erro é bem conhecido e bem comum quando estamos trabalhando com as versões mais novas do Java:

Mas você pode resolvê-lo facilmente mudando a versão de 1.8 para 1.7, ou seja, mude essas configurações do build.gradle:

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}

Para estas:

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}

Após isso clique em Sync Now e tente realizar o mesmo processo indo em "Build > Make Project", se não houver nenhum erro, parabéns, podemos continuar com nosso aprendizado.

Trabalhando com o Room

É importante ressaltar que durante muitas partes da implementação do Room, iremos trabalhar com as famosas Anotations, que significa "Anotações".

Para começar vamos desenvolver nossas classes que representam nossas entidades.

Nesta conteúdo vamos criar e trabalhar somente com uma única tabela, cujo nome será Usuario.

Criando a Entidade Usuario

Dentro da pasta principal do seu projeto, mais especificamente dentro da pasta onde se encontra o arquivo MainActivity.kt:

Clique com o botão direito na pasta principal (com.example.room) e vá em New > Package.

No card que aparecer, basta digitar o nome da sua pasta, no meu caso nomeei como model:

Dentro dessa pasta você vai criar as classes que representam as suas entidades.

Clicando com o botão direito em cima da pasta "model", vá em New > Kotlin/Class File, e crie um arquivo com o nome da sua entidade.

No meu caso criei um arquivo chamado "UsuarioModel.kt", com o seguinte conteúdo dentro:

package com.example.room.model

class UsuarioModel {

}

Acima da classe, nós iremos criar uma anotação de modo a informar ao Room que essa classe se trata de uma entidade, e fazemos isso dessa forma:

package com.example.room.model

import androidx.room.Entity

@Entity(tableName = "Usuario")
class UsuarioModel {

}

Note que dentro da anotação @Entity definimos um parâmetro chamado tableName, que nada mais é do que um nome personalizado da nossa tabela que podemos informar.

Lembrando que o parâmetro tableName da Entity é opcional, e sem ele, o nome da classe se transformaria no nome da sua tabela.

O segundo passo é definir os atributos dessa classe, para que mais tarde o Room entenda que eles se tratam de colunas:

package com.example.room.model

//Faz as importações das classes necessárias para se criar as entidades:
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

//Criamos uma anotação que vai dizer ao Room que essa classe é uma representação de uma tabela do banco de dados, uma tabela chamada "Usuario"
@Entity(tableName = "Usuario")
class UsuarioModel {

@PrimaryKey(autoGenerate = true)//Significa que o id é a chave primaria com auto incremento (autoGenerate) ativado
@ColumnInfo(name = "id_usuario")//Diz ao Room que o atributo abaixo é uma coluna, e que o nome dessa coluna será "idade"
var id_usuario: Int = 0//Esse atributo representa uma coluna do tipo Int

@ColumnInfo(name = "nome")//Diz ao Room que o atributo abaixo é uma coluna, e que o nome dessa coluna será "nome"
var nome: String = ""//Esse atributo representa uma coluna do tipo String

@ColumnInfo(name = "idade")//Diz ao Room que o atributo abaixo é uma coluna, e que o nome dessa coluna será "idade"
var idade: Int = 0//Esse atributo representa uma coluna do tipo Int

//Lembrando que os atributos acima precisam ser inicializados com valores, mas não se preocupe, pois o que estes valores não irão influenciar em nada nossa base de dados.

}

Isso significa que mais tarde, o Room se encarregará de criar uma tabela chamada "usuario" que contenha 3 colunas:

  • id
  • nome
  • idade

Observe que dentro da anotação chamada @ColumnInfo, informamos um parâmetro chamado name.

Dentro desse parâmetro nós podemos informar um nome diferente para essa coluna. Sem a declaração desse parâmetro o Room entende que o nome da variável será o nome da coluna.

Isso diz a respeito que o parâmetro name, nos da á possibilidade de criar um nome personalizado para aquela coluna.

Se você observar, os atributos estão declarados como VAR em vez de VAL.

Isso é necessário pois na camada de DAO - lá quando nós formos fazer algum insert, select, update ou delete -, precisamos passar uma instância de UsuarioModel com esses atributos preenchidos com os valores desejados.

De modo que o DAO reconheça a instancia dessa classe, e por de baixo dos panos ele possa montar a nossa Query.

Em outras linguagens de programação, e até mesmo nesse repositório, você vai observar que o processo ocorre de forma diferente, onde passamos as colunas diretamente para a classe do SQL e de lá nós mesmos montamos a Query na mão.

Entretanto, quando usamos o Room, o modus operandi é esse, e temos que seguir dessa forma, ok?

Criando o Database

Como segundo passo, precisamos construir o nosso terceiro bloco daquela estrutura que vimos logo no início deste conteúdo, que é o bloco de Database.

Para isso, você vai precisar criar um novo Package (pasta) na pasta principal do seu projeto chamado de "repository", e dentro dela você vai precisar criar a classe abstrata do banco de dados. 

No meu caso eu chamei essa classe de UsuarioDataBase.kt, veja como ficou o resultado final:

Já dentro desse arquivo, veja como ficou os comandos utilizados:

package com.example.room.repository

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.room.model.UsuarioModel

@Database(entities = [UsuarioModel::class], version = 1)
abstract class UsuarioDataBase: RoomDatabase() {

companion object{
private lateinit var INSTANCE: UsuarioDataBase

fun getDataBase(context: Context): UsuarioDataBase{
if(!::INSTANCE.isInitialized) {
synchronized(UsuarioDataBase::class) {
INSTANCE =
Room.databaseBuilder(context, UsuarioDataBase::class.java, "usuariodb").addMigrations(
MIGRATION_1_2, MIGRATION_2_3).allowMainThreadQueries().build()
}
}
return INSTANCE
}

private val MIGRATION_1_2: Migration = object : Migration(1, 2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("")
}
}

private val MIGRATION_2_3: Migration = object : Migration(1, 2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("")
}
}

}

}

Agora vamos aprender parte a parte dos comandos que acabamos de ver acima.

Logo abaixo da linha dos imports, nos deparamos com a anotação @database que conta com dois parâmetros, que são entities e version.

@Database(entities = [UsuarioModel::class], version = 1)

Quando usamos a anotação do Room chamada @database, estamos informando que este arquivo (UsuarioDataBase.kt) é o responsável por se conectar com o banco de dados.

Dentro dos colchetes da anotação nós devemos informar os entites, que nada mais são do que todos os arquivos que representam as entidades (tabela do banco de dados).

No meu caso, eu só precisei informar o UsuarioModel, pois no momento só existe uma entidade:

entities = [UsuarioModel::class]

Agora, caso você tivesse mais de um Model representando outras entidades no seu banco, você poderia inseri-los separando-os por vírgula, dessa forma:

entities = [UsuarioModel::class, ProdutoModel::class, CarrosModel::class]

O segundo parâmetro chamado de version, diz ao Room a versão atual do seu banco de dados.

Conforme você vai atualizando seu projeto, talvez você tenha a necessidade de inserir ou remover entidades, e é dessa forma que o Room saberá quando ele deve fazer isso.

Imagine, que você lançou seu projeto na versão 1 com apenas uma única tabela no banco de dados chamada de Usuario, e seu projeto teve 1000 downloads.

Meses depois, você sentiu a necessidade de atualizar seu aplicativo e criar uma nova funcionalidade capaz de gerenciar os produtos comprados por esses usuários.

Com isso, além de você criar essa nova funcionalidade, você criaria também a entidade (tabela) responsável por armazenar esses dados.

Pois bem, os novos usuários que baixaram o aplicativo pela primeira vez, já irão receber as atualizações das duas tabelas, de modo que o aplicativo funcionará normalmente.

Só que... aqueles que baixaram a primeira versão, assim que atualizarem o app, poderão encontrar problemas com a nova funcionalidade de produtos comprados.

Isso porque, o arquivo de criação do banco de dados é chamado uma única vez, e após isso, o Room não chama ele de novo mesmo que você tenha declarado essas novas entidades.

Portanto, você precisaria atualizar o numero informado no version para que os usuários que ainda estão na versão 1, possam receber as novas atualizações que você fez no seu banco.

Por de baixo dos panos, quando o usuário instala o aplicativo, o Room é chamado e é criado um banco de dados relacionado a versão que você inseriu naquele parâmetro.

Se por um acaso, você mudar aquela versão, o Room se encarregará de procurar atualizações por meio do método .addMigrations(), que está relacionado com a instância do Room, que iremos aprender mais tarde.

Já a nossa classe abstrata chamada UsuarioDataBase.kt, precisa ser herdada de RoomDatabase() para funcionar corretamente.

Pois é nela que haverão métodos e atributos que podemos utilizar para criar, e se comunicar com nosso banco de dados.

Felizmente (ou infelizmente), quando usamos o Room precisamos que a nossa classe do banco de dados seja OBRIGATORIAMENTE abstrata, ok?

Um Singleton nada mais é do que é um padrão de projeto criacional que permite a você garantir que uma classe tenha apenas uma única instância.

E estamos fazendo isso com a nossa base de dados, para que não tenhamos mais de uma intância alocadas na memoria de maneira desnecessária.

O conceito do Singleton foi aplicado dentro do Companion Object, observe:

companion object{
...

fun getDataBase(context: Context): UsuarioDataBase{
if(!::INSTANCE.isInitialized) {
synchronized(UsuarioDataBase::class) {
....
}
return INSTANCE
}

}

Perceba que estamos criando um único método chamada getDatabase que vai retornar a mesma instância do banco de dados sempre quando for chamado.

Ali nós temos também a presença do Synchronized, que é um bloco do próprio Android que reforça a ideia de que tenhamos uma única Thread tratando a instância do banco de dados.

Para saber mais sobre o Synchronizedleia este artigo.

Quando criamos o arquivo Database precisamos criar uma instância de Room, tanto para criar o banco de dados, quanto para realizar as operações dentro dele.

E isso tudo foi feito por meio desses dois comandos:

private lateinit var INSTANCE: UsuarioDataBase

...

INSTANCE = Room.databaseBuilder(context, UsuarioDataBase::class.java, "usuariodb").addMigrations(
MIGRATION_1_2, MIGRATION_2_3).allowMainThreadQueries().build()

Room.DatabaseBuilder: Chama a classe e o método responsável por criar/chamar a base de dados.

Se for a primeira vez que o usuário usa o banco de dados, ele o cria, se não, ele só vai selecionar mesmo.

Dentro dele, precisamos passar o contexto da nossa aplicação (assim como a maioria das operações do Android), e como o último parâmetro o nome do nosso banco de dados ("usuariodb").

AddMigrations: São as migrações comentadas anteriormente. Que são os métodos que o Room irá chamar quando uma nova versão for encontrada.

AllowMainThreadQueries: Reforça a ideia do Singleton, fazendo com que todas as querys aconteçam apenas em uma única Thread.

Build: Chama a função que faz a execução final do banco de dados.

Por fim nós temos os seguintes atributos que armazenam uma instância da classe anônima herdada de Migration:

private val MIGRATION_1_2: Migration = object : Migration(1, 2){//Essas são as atualizações que o Room irá executar se o usuário estiver na versão 1 e for para a 2
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("")//Aqui podemos executar qualquer query do SQL
}
}

private val MIGRATION_2_3: Migration = object : Migration(2, 3){//Essas são as atualizações que o Room irá executar se o usuário estiver na versão 2 e for para a 3
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("")//Aqui podemos executar qualquer query do SQL
}
}

Cada um desses atributos representa uma atualização que aconteceu durante o desenvolvimento do nosso projeto.

A primeira migração acontece na variável MIGRATION_1_2, que tem o objetivo de atualizar todos os usuários que ainda estão na versão 1, e indo para a versão 2 (Migration(1, 2)).

Perceba que ali dentro é executado uma query SQL, onde podemos remover tabelas, criar registros, fazer atualizações e tudo mais.

A mesma lógica segue para a variável MIGRATION_2_3.

Se o usuário estiver com a versão 1, e atualizar o aplicativo, o Room executa tanto as migrações MIGRATION_1_2, e MIGRATION_2_3.

Se o usuário estiver com a versão 2, ele só executa a migração MIGRATION_2_3.

Se o usuário estiver com a versão 3, ele não executa nenhuma migração.

Criando o DAO

Como a DAO é uma classe que é feita para acessar e enviar dados, nada mais justo que organiza-la dentro de uma nova Package (pasta).

Neste caso, foi criada uma nova Package com o nome de "dao" dentro de Package "repository":

Dentro dessa nova pasta que você criou, vamos criar um novo arquivo de INTERFACE chamado de UsuarioDAO.kt:

package com.example.room.repository.dao

interface UsuarioDAO {
}

A partir de agora, vamos fazer as devidas modificações:

package com.example.room.repository.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.room.model.UsuarioModel

@Dao
interface UsuarioDAO {

@Insert
fun insertUser(usuario: UsuarioModel): Long

@Update
fun updateUser(usuario: UsuarioModel): Int

@Delete
fun deleteUser(id: Int)

@Query("SELECT * FROM Usuario WHERE id = :id")
fun get(id: Int): UsuarioModel

@Query("SELECT * FROM Usuario")
fun getAll(): List<UsuarioModel>

}

Vejamos abaixo o que cada comando faz:

@Dao: Indica que essa classe deste arquivo é uma DAO, e se encarrega de fazer todas as operações de Insert, Select, Delete e Update relacionados com a tabela Usuario.

@Insert: Anotação que informa ao Room que o método que é informado abaixo, é responsável por fazer inserções na tabela Usuario.

Diferente de outras linguagens de programação ou métodos do Kotlin, em que devemos escrever uma Query como "INSERT INTO ...", aqui acontece de forma automática.

Bastando apenas que você envie como parâmetro uma instância da classe UsuarioModel (que contém as colunas), onde cada atributo existente naquela classe esteja com seus respectivos valores alterados.

É dessa forma que o Room trabalha, pois de baixo dos panos, ele se encarrega de criar os comandos de inserção manual.

Retorno Long? É importante ressaltar que o método insert não precisa de retorno. Nesse caso o Long representa um valor numérico que conta o total de registros que foram inseridos.

É por meio do Long que saberemos que o insert foi executado com sucesso, pois podemos verificar se Long > 0, e caso positivo a inserção foi realizada.

@Update: Anotação que informa ao Room que o método abaixo é responsável por fazer atualizações na tabela Usuario.

Segue a mesma linha de raciocínio de @Insert.

Retorno Int? Assim como no insert, ele também é opcional, e diferente dele, seu retorno é tipo Int.

É por meio do Long que saberemos que o insert foi executado com sucesso, pois podemos verificar se Long > 0, e caso positivo a inserção foi realizada.

@Delete: notação que informa ao Room que o método abaixo é responsável por fazer remoções na tabela Usuario.

Aqui basta que você envie como parâmetro o id que você quer que seja removido.

No caso do delete, ele já entende que o parâmetro id é o que desejamos remover, e por de baixo dos panos, esse atrelamento acontece de forma automática.

@Query: Anotação que informa ao Room que o método abaixo é responsável por fazer consultas (select) na tabela Usuario.

Aqui nós devemos informar dentro dos parêntesis a query do SQL.

Observe que estamos passando o parâmetro que recebemos no método get para dentro da query por meio dos dois pontos :id.

Observe também que o retorno desse método é do tipo UsuarioModel.

Na primeira Query: Como se trata de apenas um único registro, ele só vai retornar um único id, um único nome, e uma única idade.

De modo que o Room salve esses valores dentro dos atributos em uma nova instância de UsuarioModel para que você consiga selecionar esses atributos mais tarde.

Na segunda Query: Veja que o retorno da função GetAll, é uma lista, e isso é obvio, pois podemos ter mais de um registro na tabela.

E essa lista nada mais é do que uma instância da classe UsuarioModel, e aqui já sabemos como funciona esse processo ;)

Simplicidade e Praticidade

Observe como dentro do DAO tudo transcorre de uma maneira simples, não acha?

Dessa forma conseguimos abstrair todos aqueles códigos de múltiplas linhas repetidas, como visto neste repositório, para algo mais simples e compacto.

É claro que por de baixo dos panos, o Room faz uso da classe do SQLOpenHelper, logo, tudo o que escrevemos aqui acaba se transformando em algo muito parecido com o arquivo que vimos no link acima.

A diferença é que quem se preocupa com isso é o ROOM e não você :D

Referência de DAO em Database

Para finalizar, você precisa fazer uma referência de UsuarioDAO dentro de UsuarioDataBase da seguinte forma:

abstract class UsuarioDataBase: RoomDatabase() {

abstract fun usuarioDAO(): UsuarioDAO//Referência de UsuarioDAO, pois iremos selecionar essa classe por meio da instancia do banco

companion object{
...
}

}

Com isso terminamos a implementação da lógica do nosso Room 🥳

A partir de agora vamos trabalhar dentro do MainActivity.kt de modo a chamar o banco de dados e fazer as devidas operações.

Usando Banco de Dados

Com todas as 3 camadas do Room implementadas, chegou o momento de aprendermos a chamar nossos métodos de DAO para selecionar, inserir, atualizar e remover registros.

Tenha em mente que diferente de outros tutoriais, aulas e conteúdos que vemos por aí na internet, aqui você não vai se deparar com TextView, RecyclerView, Listas, Campos de Textos e tudo mais.

Uma vez que o intuito deste conteúdo é mostrar da forma mais crua possível o funcionamento do Room, e nós iremos fazer isso dentro do método OnCreate da MainActivity, ok?

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

//Insira aqui dentro os códigos que você vai aprender adiante...
}
}

O processo do Room é bem fácil, em primeiro lugar, você precisa possuir uma instância de UsuarioDataBase de forma a chamar também a classe de UsuarioDao, e podemos fazer isso da seguinte forma:

//Instancia de Room
val usuarioDatabase = UsuarioDataBase.getDataBase(this).usuarioDAO()//Aqui estamos instanciando a classe do Room, ao mesmo tempo que estamos retornando a classe de usuarioDao

Em seguida, como nosso banco de dados se encontra vazio, nós iremos fazer a nossa primeira inserção, e podemos fazer isso da seguinte forma:

//Insert, observe como fazemos o insert, passando uma instância de UsuarioModel, com os atributos alterados:
val retornoInsert = usuarioDatabase.insertUser(UsuarioModel().apply {
this.nome = "Micilini Roll"
this.idade = 23
})

Após a inserção, que tal atualizar o registro que acabou de ser inserido?

//Update, observe como fazemos o update, bastando passar o id_usuario, que o Room já entende que é ele que gostaríamos de Atualizar.
val retornoUpdate = usuarioDatabase.updateUser(UsuarioModel().apply {
this.id_usuario = 1
this.nome = "Micilini Roll - Atualizado"
})
//Após a atualização a idade será trocada para o valor 0, uma vez que por esse meio não é possível fazer uma atualização parcial, ou seja, somente do nome

Tenha em mente que o Room não vai atualizar parcialmente um determinado registro, apesar de haver meios de se fazer isso.

No caso acima, a idade daquele registro será marcada como 0 em vez de ficar com 23.

Agora, para remover, basta usar o método:

//Delete, observe que fazemos o delete, bastando apenas informar o id_usuario, não é necessário.
usuarioDatabase.deleteUser(UsuarioModel().apply {
this.id_usuario = 2
})//Note que o próximo id_usuario que será inserido será o 3 em diante, pois o 2 foi apagado e não existirá mais...

Note que o próximo id_usuario que será inserido será o 3 em diante, pois o 2 foi apagado e não existirá mais.

Agora, vamos conhecer o método de retorno de um único registro:

//Select, observe como é simples fazer uma consulta no banco de dados a fim de retornar dados:
val retornoSelectUnico = usuarioDatabase.get(1)//Retorna o primeiro registro

Log.d("Retorno Único", "id_usuario: ${retornoSelectUnico.id_usuario}, nome: ${retornoSelectUnico.nome}, idade: ${retornoSelectUnico.idade}")

Log.d, é uma classe do Kotlin que nos ajuda a consultar algumas informações no console do LogCat, para saber mais sobre ele, veja este conteúdo.

Por fim, vamos aprender a retornar todos os registros:

val retornoSelectMultiplo = usuarioDatabase.getAll()//Retorna todos os registros

for(item in retornoSelectMultiplo){
Log.d("Retorno Múltiplo", "id_usuario: ${item.id_usuario}, nome: ${item.nome}, idade: ${item.idade}")
}

Com isso você aprendeu a utilizar o banco de dados Room, e já está pronto para implementa-lo no seu projeto 😎

Realizando Inserts, Updates e Deletes pela @Query

Sim, isso mesmo que você leu no título acima, você pode fazer todas essas operações usando a anotação @Query.

E não, ela não é exclusivamente dedicada a querys do tipo SELECT, mas como também nos da a possibilidade de fazer inserções, atualizações e remoções 😄

Sendo um ótimo aliado quando queremos realizar atualizações (updates) parciais.

Vejamos alguns exemplos:

@Query("INSERT INTO Usuario (nome, idade) VALUES (:nome, :idade)")
fun insertManual(nome: String, idade: Int)

@Query("UPDATE Usuario SET nome = :nome, idade = :idade WHERE id_usuario = :id_usuario")
fun updateManual(id_usuario: Int, nome: String, idade: Int)

@Query("UPDATE Usuario SET nome = :nome WHERE id_usuario = :id_usuario")//Note que aqui podemos fazer o update parcial, já que estamos realizando uma operação manual
fun updateManualParcial(id_usuario: Int, nome: String)

@Query("DELETE FROM Usuario WHERE id_usuario > 15")//Aqui estamos realizando um delete manual, sem a necessidade de passar parâmetros, mas caso desejar você pode passar
fun deleteManual()

Para chamar cada uma dessas funções é bem simples, e o ponto bom é que não precisa do UsuarioModel para trafegar dados.

//Realizando Insert, Update e Delete manuais:

usuarioDatabase.insertManual("Gabriel Solano", 30)//Também podemos fazer insertManual por meio do @Query, bastando apenas digitar "INSERT INTO..."

usuarioDatabase.updateManual(3, "Gabriel Solano - Atualizado", 28)//Exemplo de atualização completa

usuarioDatabase.updateManualParcial(4, "Mic Rollis")//Exemplo de atualização parcial

usuarioDatabase.deleteManual()//Remove todos os id_usuario que forem maiores do que 15

Como acessar o banco de dados da sua aplicação em Kotlin?

Bem, existem duas formas de acessar o banco de dados gerenciado pela biblioteca Room na sua aplicação.

Método 1) Fazendo download do banco

Uma das formas mais fáceis de visualizar os dados que você está salvando no banco é fazendo o download do mesmo para uma pasta de fácil acesso no seu computador, para posteriormente você abrir usando o DB Browser (SQLite).

Primeiro de tudo: Certifique-se de que você tem o aplicativo instalado no emulador, e que o mesmo se encontra aberto.

Para isso, selecione a opção "Device File Explorer" existente no canto inferior direito do Android Studio.

Uma nova janela irá se abrir, basta que você expanda Data > Data, e procure o nome da sua aplicação, no meu caso ela se chama com.example.room.

Ali dentro haverá 3 pastas: cachecode_cachedatabases.

Abra a pasta databases, e lá dentro você verá um arquivo com o nome do seu banco de dados, no meu caso ele está nomeado como usuariodb.

Basta clicar com o botão direito em cima deste arquivo e ir em Save As...

E salve-o em uma pasta do seu computador para que posteriormente você consiga abrir com o DB Browser (SQLite).

Método 2) Via App Inspector

Dentro do Android Studio, no canto inferior, você verá algumas opções como: Version Control, Run, Debug, Todo e App Inspector.

Clique em App Inspector (certifique-se de que o seu app está aberta junto com o emulador).

Por fim basta que você selecione o aplicativo atual (com.example.room), e clique duas vezes na tabela que você gostaria de visualizar os registros, como mostra a figura abaixo:

Com isso você já consegue visualizar todo o banco de dados da sua aplicação.

Conclusão

Neste conteúdo você viu o essencial do uso da Room no Kotlin.

É claro que existem diversas outras anotações e possibilidades que podemos realizar com Room.

Até o próximo conteúdo 😍