A maioria dos aplicativos possui dados que precisam ser mantidos, mesmo depois que o usuário fecha o aplicativo. Por exemplo, o aplicativo pode armazenar uma lista de reprodução, um inventário de itens do jogo, registros de despesas e receitas, um catálogo de constelações ou dados de sono ao longo do tempo. Normalmente, você usaria um banco de dados para armazenar dados persistentes.
Room
é uma biblioteca de banco de dados que faz parte do Android Jetpack. Room
cuida de muitas das tarefas de instalação e
configuração de um
banco de dados e torna possível que seu aplicativo interaja com o banco de dados usando chamadas de função
comuns. Por baixo do capô, o Room
é uma camada de abstração sobre um banco de dados SQLite. A
terminologia da Room
e a sintaxe de consulta para consultas mais complexas seguem o modelo SQLite.
A imagem abaixo mostra como o banco de dados Room
se encaixa na arquitetura geral recomendada
neste curso.
Você deve estar familiarizado com:
LiveData
e seus observadores. Esses
tópicos de componentes de arquitetura são abordados em um tutorial anterior deste curso.Room
para manter os dados. Room
com uma interface para dados de sono noturno.Neste tutorial, você constrói a parte do banco de dados de um aplicativo que rastreia a qualidade do sono. O aplicativo usa um banco de dados para armazenar dados de sono ao longo do tempo.
O aplicativo possui duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.
A primeira tela, mostrada à esquerda, possui botões para iniciar e parar o rastreamento. A tela mostra todos os dados de sono do usuário. O botão Clear exclui permanentemente todos os dados que o aplicativo coletou para o usuário.
A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono. No aplicativo, a classificação é representada numericamente. Para fins de desenvolvimento, o aplicativo mostra os ícones de rosto e seus equivalentes numéricos.
O fluxo do usuário é o seguinte:
Este aplicativo usa uma arquitetura simplificada, conforme mostrado abaixo no contexto da arquitetura completa. O aplicativo usa apenas os seguintes componentes:
LiveData
SleepTrackerFragment
, mas nenhum dado. Os botões não respondem aos toques. build.gradle
de nível de projeto,
observe as variáveis que especificam as versões da biblioteca. As versões usadas no aplicativo inicial
funcionam bem juntas e funcionam bem com este aplicativo. Quando você terminar este tutorial, o Android Studio
pode solicitar que você atualize algumas das versões. Depende de você se deseja atualizar ou permanecer com as
versões que estão no aplicativo. Se você encontrar erros de compilação "estranhos", tente usar a combinação de
versões de biblioteca que o aplicativo de solução final usa.Room
, e as dependências para corrotinas. database
, para todos os códigos
relacionados ao banco de dados Room
. sleepquality
e
sleeptracker
contém o fragmento, o modelo de vistas e a fábrica
de modelos de vistas para cada tela.Util.kt
file, que contém funções para
ajudar a exibir dados de qualidade do sono. Algum código está comentado, pois faz referência a um modelo de
vistas que você cria posteriormente. SleepDatabaseTest.kt
). Você usará este teste para
verificar se o banco de dados funciona conforme o esperado.No Android, os dados são representados em classes de dados, e os dados são acessados e modificados usando chamadas de função. No entanto, no mundo do banco de dados, você precisa de entidades e consultas.
A Room
faz todo o trabalho duro para você ir das classes de dados Kotlin às entidades que podem
ser armazenadas em tabelas SQLite e de declarações de funções a consultas SQL.
Você deve definir cada entidade como uma classe de dados anotada e as interações como uma interface anotada, um
objeto de acesso a dados (DAO). Room
usa essas classes anotadas para criar tabelas no
banco de dados e consultas que atuam no banco de dados.
Nesta tarefa, você define uma noite de sono como uma classe de dados anotada.
Por uma noite de sono, você precisa registrar a hora de início, hora de término e uma classificação de qualidade.
E você precisa de uma ID para identificar exclusivamente a noite.
database
, encontre e abra o arquivo SleepNight.kt
. SleepNight
com parâmetros para um ID, uma hora de início (em
milissegundos), uma hora de término (em milissegundos) e uma classificação numérica da qualidade do sono.
sleepQuality
, então defina-o como -1
, indicando que nenhum
dado de qualidade foi coletado. data class SleepNight(
var nightId: Long = 0L,
val startTimeMilli: Long = System.currentTimeMillis(),
var endTimeMilli: Long = startTimeMilli,
var sleepQuality: Int = -1
)
@Entity
. Nomeie a tabela como
daily_sleep_quality_table
. O argumento para tableName
é opcional, mas recomendado.
Você pode procurar outros argumentos na documentação.Entity
e
todas as outras anotações da biblioteca androidx
.@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)
nightId
como a chave primária, anote a propriedade nightId
com
@PrimaryKey
. Defina o parâmetro autoGenerate
como true
para que
Room
gere o ID para cada entidade. Isso garante que o ID de cada noite seja único.
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...
@ColumnInfo
. Personalize os nomes das propriedades usando
os parâmetros mostrados abaixo. import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,
@ColumnInfo(name = "start_time_milli")
val startTimeMilli: Long = System.currentTimeMillis(),
@ColumnInfo(name = "end_time_milli")
var endTimeMilli: Long = startTimeMilli,
@ColumnInfo(name = "quality_rating")
var sleepQuality: Int = -1
)
Nesta tarefa, você define um objeto de acesso a dados (DAO). No Android, o DAO fornece métodos convenientes para inserir, excluir e atualizar o banco de dados.
Ao usar um banco de dados Room
, você consulta o banco de dados definindo e chamando funções Kotlin
em seu código. Essas funções Kotlin são mapeadas para consultas SQL. Você define esses mapeamentos em um DAO
usando anotações e Room
cria o código necessário.
Pense em um DAO como definindo uma interface personalizada para acessar seu banco de dados.
Para operações de banco de dados comuns, a biblioteca Room
oferece anotações de conveniência, como
@Insert
, @Delete
e @Update
. Para todo o resto, existe a anotação
@Query
. Você pode escrever qualquer consulta que seja compatível com SQLite.
Como um bônus adicional, conforme você cria suas consultas no Android Studio, o compilador verifica suas consultas SQL em busca de erros de sintaxe.
Para o banco de dados do rasthreador de sono de noites de sono, você precisa ser capaz de fazer o seguinte:
database
, abra SleepDatabaseDao.kt
.interface
SleepDatabaseDao
está anotada com @Dao
. Todos
os DAOs precisam ser anotados com a palavra-chave @Dao
. @Dao
interface SleepDatabaseDao {}
@Insert
. Abaixo de @Insert
,
adicione uma função insert()
que leva uma instância da classe Entity
SleepNight
como seu argumento. Room
gerará todo o
código necessário para inserir o SleepNight
no banco de dados. Quando você chama
insert()
do seu código Kotlin, Room
executa uma consulta SQL para inserir a entidade
no banco de dados. (Observação: Você pode chamar a função de qualquer maneira).@Insert
fun insert(night: SleepNight)
@Update
com uma função update()
para um
SleepNight
. A entidade que é atualizada é a entidade que possui a mesma chave daquela que foi
passada. Você pode atualizar algumas ou todas as outras propriedades da entidade.@Update
fun update(night: SleepNight)
Não há anotação de conveniência para a funcionalidade restante, então você deve usar a anotação
@Query
e fornecer consultas SQLite.
@Query
com uma função get()
que leva uma função
Long
key
argumento e retorna um SleepNight
anulável.
Você verá um erro para um parâmetro ausente.@Query
fun get(key: Long): SleepNight?
@Query
. Torne-a uma String
que é uma consulta SQLite.daily_sleep_quality_table
WHERE
nightId
corresponde ao argumento :key
. :key
. Você usa a notação de dois pontos na consulta para fazer referência a argumentos na função.
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
@Query
com uma função clear()
e uma consulta SQLite para usar
DELETE
em toda daily_sleep_quality_table
. Esta consulta não exclui a própria tabela.
@Delete
exclui um item, e você pode usar @Delete
e fornecer uma
lista de noites a serem excluídas. A desvantagem é que você precisa buscar ou saber o que está na tabela. A
anotação @Delete
é ótima para excluir entradas específicas, mas não é eficiente para limpar todas
as entradas de uma tabela.@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
@Query
com uma função getTonight()
. Torne o SleepNight
retornado por getTonight()
anulável, de modo que a função possa tratar com o caso em que a tabela
está vazia. (A tabela está vazia no início e depois que os dados são apagados).nightId
em ordem decrescente. Use LIMIT 1
para retornar
apenas um elemento.@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
@Query
com uma função getAllNights()
: daily_sleep_quality_table
, ordenadas
em ordem decrescente. getAllNights()
retorne uma lista de entidades SleepNight
como
LiveData
. Room
mantém este LiveData
atualizado, o que significa que
você somente precisa obter os dados explicitamente uma vez.LiveData
de androidx.lifecycle.LiveData
.@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
Nesta tarefa, você cria um banco de dados Room
que usa a Entity
e o DAO que você
criou na tarefa anterior.
Você precisa criar uma classe de recipiente de banco de dados abstrata, anotada com @Database
.
Essa classe tem um método que cria uma instância do banco de dados se o banco de dados não existir ou retorna
uma referência a um banco de dados existente.
Obtendo um banco de dados Room
é um pouco complicado, então aqui está o processo geral antes de
começar com o código:
public abstract
que extends RoomDatabase
. Esta classe deve atuar
como um recipiente do banco de dados. A classe é abstrata, pois Room
cria a implementação.@Database
. Nos argumentos, declare as entidades do banco de dados e defina o
número da versão.companion
, defina um método ou propriedade abstrata que retorna um
SleepDatabaseDao
. Room
gerará o corpo.Room
para o aplicativo inteiro,
portanto, torne o RoomDatabase
em um singleton. Room
para criar o banco de dados apenas se o banco de
dados não existir. Caso contrário, retorne o banco de dados existente. database
, abra SleepDatabase.kt
. abstract
chamada SleepDatabase
que estende
RoomDatabase
.@Database
.@Database()
abstract class SleepDatabase : RoomDatabase() {}
@Database
requer
vários argumentos, para que Room
possa construir o banco de dados. SleepNight
como o único item com a lista de entities
. version
como 1
. Sempre que você alterar o esquema, terá que aumentar o
número da versão. exportSchema
como false
, de modo a não manter backups de histórico de
versão do esquema.entities = [SleepNight::class], version = 1, exportSchema = false
SleepDatabaseDao
. Você pode ter vários DAOs. abstract val sleepDatabaseDao: SleepDatabaseDao
companion
. O objeto complementar permite que os clientes acessem
os métodos para criar ou obter o banco de dados sem instanciar a classe. Visto que o único propósito desta
classe é fornecer um banco de dados, não há razão para instanciá-lo. companion object {}
companion
, declare uma variável privada anulável INSTANCE
para o
banco de dados e inicialize-a como null
. A variável INSTANCE
manterá uma referência
para o banco de dados, uma vez criada. Isso ajuda a evitar abrir repetidamente conexões com o banco de dados,
o que é caro.Anote INSTANCE
com @Volatile
. O valor de uma variável volátil nunca será armazenado
em cache e todas as gravações e leituras serão feitas de e para a memória principal. Isso ajuda a garantir que o
valor de INSTANCE
esteja sempre atualizado e o mesmo para todos as threads de execução. Isso
significa que as alterações feitas por uma thread em INSTANCE
são visíveis para todos as outras
threads imediatamente, e você não obtém uma situação em que, digamos, dois threads atualizam cada uma a mesma
entidade em um cache, o que criar um problema.
@Volatile
private var INSTANCE: SleepDatabase? = null
INSTANCE
, ainda dentro do objeto companion
, defina um método
getInstance()
com um parâmetro Context
que o banco de dados o construtor precisará.
Retorne um tipo SleepDatabase
. Você verá um erro, pois getInstance()
ainda não está
retornando nada.fun getInstance(context: Context): SleepDatabase {}
getInstance()
, adicione um bloco synchronized{}
. Passe this
para que você possa acessar o contexto.synchronized
significa que apenas uma thread de execução por vez pode entrar
neste bloco de código, o que garante que o banco de dados seja inicializado apenas uma vez.synchronized(this) {}
INSTANCE
para uma variável local
instance
. Isso é para tirar vantagem da conversão
inteligente, que
somente está disponível para variáveis locais.var instance = INSTANCE
synchronized
, return instance
no final do bloco
synchronized
. Ignore o erro de incompatibilidade de tipo de retorno; você nunca retornará nulo
quando terminar.return instance
return
, adicione uma instrução if
para verificar se a
instance
é nula, ou seja, ainda não há banco de dados.if (instance == null) {}
instance
for null
, use o construtor de banco de dados para obter um banco de
dados. No corpo da instrução if
, invoque Room.databaseBuilder
e forneça o contexto
que você passou, a classe do banco de dados e um nome para o banco de dados,
sleep_history_database
. Para remover o erro, você terá que adicionar uma estratégia de migração e
build()
nas etapas a seguir.instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database")
.fallbackToDestructiveMigration()
..fallbackToDestructiveMigration()
.build()
..build()
INSTANCE = instance
como a etapa final dentro da instrução if
. INSTANCE = instance
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {
abstract val sleepDatabaseDao: SleepDatabaseDao
companion object {
@Volatile
private var INSTANCE: SleepDatabase? = null
fun getInstance(context: Context): SleepDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
Agora você tem todos os blocos de construção para trabalhar com o banco de dados do Room
. Este
código é compilado e executado, mas você não tem como saber se ele realmente funciona. Portanto, este é um bom
momento para adicionar alguns testes básicos.
Nesta etapa, você executa os testes fornecidos para verificar se o banco de dados funciona. Isso ajuda a garantir que o banco de dados funcione antes de você construí-lo. Os testes fornecidos são básicos. Para um aplicativo de produção, você exercitaria todas as funções e consultas em todos os DAOs.
O aplicativo inicial contém uma pasta androidTest. Esta pasta androidTest contém testes de unidade que envolvem instrumentação Android, que é uma maneira sofisticada de dizer que os testes precisam da estrutura Android, portanto, você precisa executar os testes em um dispositivo físico ou virtual. Claro, você também pode criar e executar testes de unidade puros que não envolvem a estrutura do Android.
Cmd+/
ou Control+/
. Aqui está uma rápida execução do código de teste, pois é outra parte do código que você pode reutilizar:
SleepDabaseTest
é uma classe de teste.@RunWith
identifica o executor de teste, que é o programa que configura e executa os
testes. @Before
é executada e cria um
SleepDatabase
na memória com o SleepDatabaseDao
. "Na memória" significa que este
banco de dados não é salvo no sistema de arquivos e será excluído após a execução dos testes.allowMainThreadQueries
. Por padrão, você obtém um erro se tentar executar consultas na thread
principal. Este método permite que você execute testes na thread principal, o que você deve fazer apenas
durante o teste. @Test
, você cria, insere e recupera um
SleepNight
e afirma que eles são iguais. Se algo der errado, lance uma exceção. Em um teste real,
você teria vários métodos @Test
.@After
é executada para fechar o banco de
dados.Como todos os testes foram aprovados, agora você sabe vários assuntos:
SleepNight
no banco de dados.SleepNight
.SleepNight
tem o valor correto para a qualidade. Projeto Android Studio: TrackMySleepQualityRoomAndTesting
Ao testar um banco de dados, você precisa exercitar todos os métodos definidos no DAO. Para concluir o teste,adicione e execute testes para exercitar os outros métodos DAO.
@Entity
. Defina as propriedades anotadas
com @ColumnInfo
como colunas nas tabelas.@Dao
. O DAO mapeia
funções Kotlin para consultas de banco de dados. @Insert
, @Delete
e @Update
.
@Query
com uma string de consulta SQLite como um parâmetro para qualquer outra
consulta.
getInstance()
que retorna um banco de dados.Documentação do desenvolvedor Android:
RoomDatabase
Database
(anotações)Room
Roomdatabase.Builder
SQLiteDatabase
Dao
Room
biblioteca de persistênciaOutra documentação e artigos:
Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.
Como você indica que uma classe representa uma entidade para armazenar em um banco de dados Room
?
DatabaseEntity
.@Entity
.@Database
.RoomEntity
e também anotar a classe com @Room
.O DAO (objeto de acesso a dados) é uma interface que o Room
usa para mapear funções Kotlin para
consultas de banco de dados.
Como você indica que uma interface representa um DAO para um banco de dados Room
?
RoomDAO
.EntityDao
, então implemente o método DaoConnection()
.
@Dao
.@RoomConnection
.Quais das afirmações a seguir são verdadeiras sobre o banco de dados Room
? Escolha todas as opções
aplicáveis.
Room
como classes de dados anotadas.LiveData
de uma consulta, Room
manterá o LiveData
atualizado se o LiveData
mudar.Room
deve ter um, e apenas um, DAO.Room
, torne-a uma subclasse de
RoomDatabase
e anote-a com @Database
. Qual das seguintes anotações você pode usar em sua interface @Dao
? Escolha todas as opções
aplicáveis.
@Get
@Update
@Insert
@Query
Como você pode verificar se seu banco de dados está funcionando? Selecione tudo que se aplica.
Entity
.verifyDatabase()
fornecida pela biblioteca Room
.Comece para a próxima lição:
Para obter enlaces para outros tutoriais neste curso, consulte a página de destino dos tutoriais Fundamentos de Android em Kotlin.