1. Introdução
O que é o Datastore?
O DataStore é uma solução nova e melhorada de armazenamento de dados que visa substituir SharedPreferences. Criado com base em corrotinas e fluxo Kotlin, o DataStore oferece duas implementações diferentes: Proto DataStore, que permite armazenar objetos tipados (com suporte de buffers de protocolo) e Preferences DataStore, que armazena pares de valores-chave. Os dados são armazenados de forma assíncrona, consistente e transacional, superando algumas das desvantagens de SharedPreferences.
O que você aprenderá
- O que é o DataStore e por que usá-lo.
- Como adicionar o DataStore ao seu projeto.
- As diferenças entre o Preferences e o Proto DataStore e as vantagens de cada um.
- Como usar o Proto DataStore.
- Como migrar de SharedPreferences para o Proto DataStore.
O que você criará
Neste codelab, você começará com um app de amostra que exibe uma lista de tarefas que podem ser filtradas pelo status concluído e podem ser classificadas por prioridade e prazo.
A sinalização booleana para o filtro Mostrar tarefas concluídas é salva na memória. A ordem de classificação é mantida no disco usando um objeto SharedPreferences
.
Como o DataStore tem duas implementações diferentes: Preferences DataStore e Proto DataStore, você vai aprender a usar o Proto DataStore seguindo estas tarefas em cada implementação:
- Persistir o filtro de status concluído no DataStore.
- Migrar a ordem de classificação de SharedPreferences para o DataStore.
Também recomendamos que você consulte o codelab Preferences DataStore para entender melhor a diferença entre os dois.
O que é necessário
- Android Studio Arctic Fox.
- Familiaridade com estes componentes da arquitetura: LiveData, ViewModel, Vinculação de visualizações e com a arquitetura sugerida no Guia para a arquitetura do app.
- Familiaridade com corrotinas e fluxo do Kotlin (links em inglês).
Para uma introdução aos componentes da arquitetura, consulte o codelab Room com View. Para uma introdução ao fluxo, confira o codelab Corrotinas avançadas com fluxo do Kotlin e LiveData.
2. Etapas da configuração
Nesta etapa, você vai fazer o download do código para o codelab inteiro e executar um app simples de exemplo.
Para começar o mais rápido possível, preparamos um projeto inicial para você desenvolver.
Se você tiver o git instalado, basta executar o comando abaixo. Para verificar se o git está instalado, digite git --version
no terminal ou na linha de comando e verifique se ele é executado corretamente.
git clone https://github.com/googlecodelabs/android-datastore
O estado inicial fica na ramificação master
. O código da solução está na ramificação proto_datastore
(link em inglês).
Se você não tiver o git, clique no botão abaixo para fazer o download de todo o código deste codelab:
Fazer o download do código-fonte
- Descompacte o código e abra o projeto no Android Studio Arctic Fox.
- Execute a configuração de execução do app em um dispositivo ou emulador.
O app vai ser executado e exibirá a lista de tarefas:
3. Visão geral do projeto
O app permite que você veja uma lista de tarefas. Cada tarefa tem as seguintes propriedades: nome, status concluído, prioridade e prazo.
Para simplificar o código com que precisamos trabalhar, o app permite que você faça apenas duas ações:
- Ativar ou desativar a visibilidade de Mostrar tarefas concluídas: por padrão, as tarefas ficam ocultas
- Classificar as tarefas por prioridade, por prazo ou por prazo e prioridade
O app segue a arquitetura recomendada no Guia para a arquitetura do app. Veja o que você vai encontrar em cada pacote:
data
- A classe modelo
Task
. - Classe
TasksRepository
: responsável por fornecer as tarefas. Para simplificar, ele retorna dados fixados no código e os expõe usando umFlow
para representar um cenário mais realista. - A classe
UserPreferencesRepository
que contém aSortOrder
, definida como umaenum
. A ordem de classificação atual é salva em SharedPreferences como umaString
, com base no nome do valor da enumeração. Ela expõe métodos síncronos para salvar e acessar a ordem de classificação.
ui
- Classes relacionadas à exibição de uma
Activity
usando umaRecyclerView
. - A classe
TasksViewModel
é responsável pela lógica da IU.
O TasksViewModel
contém todos os elementos necessários para criar os dados que precisam ser exibidos na IU: a lista de tarefas, as sinalizações showCompleted
e sortOrder
, incluídas em um objeto TasksUiModel
. Toda vez que um desses valores mudar, vamos precisar criar um novo TasksUiModel
. Para isso, combinamos três elementos:
- Um
Flow<List<Task>>
extraído doTasksRepository
. - Um
MutableStateFlow<Boolean>
que contém a sinalizaçãoshowCompleted
mais recente, que é mantida apenas na memória. - Um
MutableStateFlow<SortOrder>
que contém o valor mais recente dasortOrder
.
Para garantir que estamos atualizando a IU corretamente, um LiveData<TasksUiModel>
vai ser exposto apenas quando a atividade for iniciada.
Temos alguns problemas com nosso código:
- Bloqueamos a linha de execução de IU na E/S do disco ao inicializar
UserPreferencesRepository.sortOrder
. Isso pode resultar em instabilidade da IU. - A sinalização
showCompleted
é mantida somente na memória. Portanto, ela vai ser redefinida sempre que o usuário abrir o app. Assim como aSortOrder
, ela precisa sobreviver ao fechamento do app. - Estamos usando SharedPreferences para persistir os dados, mas um
MutableStateFlow
, que modificamos manualmente, é mantido na memória para podermos receber notificações de mudanças. Esse processo pode ser facilmente corrompido se o valor for modificado em algum outro lugar do aplicativo. - Em
UserPreferencesRepository
, disponibilizamos dois métodos para atualizar a ordem de classificação:enableSortByDeadline()
eenableSortByPriority()
. Esses dois métodos dependem do valor da ordem de classificação atual. No entanto, se um for chamado antes do outro terminar, o resultado vai ser o valor final errado. Além disso, esses métodos podem resultar em instabilidades na IU e violações no modo restrito à medida que são chamados na linha de execução de IU.
Embora as sinalizações showCompleted
e sortOrder
sejam mostradas para o usuário, elas são representadas como dois objetos diferentes. Uma das nossas metas é unificar essas duas sinalizações em uma classe UserPreferences
.
Vamos ver como usar o DataStore para nos ajudar com esses problemas.
4. DataStore: noções básicas
Muitas vezes, você pode precisar armazenar conjuntos de dados pequenos ou simples. Você pode ter usado SharedPreferences antes para fazer isso, mas essa API também tem várias desvantagens. O objetivo da biblioteca Jetpack DataStore é resolver esses problemas com a criação de uma API simples, segura e assíncrona para armazenar dados. Ela oferece duas implementações diferentes:
- Preferences DataStore
- Proto DataStore
Recurso | SharedPreferences | PreferencesDataStore | ProtoDataStore |
API Async | ✅ (apenas para ler valores alterados, usando um listener) | ✅ (via | ✅ (via |
API Synchronous | ✅ (mas não é seguro para chamadas na linha de execução de IU) | ❌ | ❌ |
É seguro para chamadas na linha de execução de IU | ❌(1) | ✅ (o trabalho é movido internamente para | ✅ (o trabalho é movido internamente para |
Pode sinalizar erros | ❌ | ✅ | ✅ |
Protegido contra exceções de tempo de execução | ❌(2) | ✅ | ✅ |
Tem uma API transacional com garantias de consistência forte | ❌ | ✅ | ✅ |
Gerencia a migração de dados | ❌ | ✅ | ✅ |
Segurança de tipo | ❌ | ❌ | ✅ com buffers de protocolo |
(1) SharedPreferences tem uma API síncrona que pode parecer segura para chamadas na linha de execução de IU, mas que realiza operações de E/S de disco. Além disso, apply()
bloqueia a linha de execução de IU em fsync()
. Chamadas fsync()
pendentes vão ser acionadas sempre que um serviço for iniciado ou interrompido e toda vez que uma atividade for iniciada ou interrompida em qualquer lugar do aplicativo. A linha de execução de IU é bloqueada durante chamadas fsync()
pendentes programadas por apply()
, geralmente sendo uma fonte de erros ANRs.
(2) SharedPreferences gera erros de análise como exceções de tempo de execução.
Preferences x Proto DataStore
Embora tanto o Preferences quanto o Proto DataStore permitam salvar dados, eles fazem isso de maneiras diferentes:
- O Preference DataStore, assim como SharedPreferences, acessa dados com base em chaves, sem definir um esquema antecipadamente.
- O Proto DataStore define o esquema usando buffers de protocolo. Usar Protobufs permite manter dados fortemente categorizados. Eles são mais rápidos, menores, simples e menos ambíguos que XML e outros formatos de dados semelhantes. Embora o Proto DataStore exija que você aprenda um novo mecanismo de serialização, acreditamos que a vantagem de categorização fornecida por ele valha a pena.
Room x DataStore
Se você precisa de atualizações parciais, integridade referencial ou conjuntos de dados grandes/complexos, use o Room em vez do DataStore. O DataStore é ideal para conjuntos de dados pequenos e simples e não oferece suporte a atualizações parciais ou integridade referencial.
5. Proto DataStore: visão geral
Um dos pontos negativos de SharedPreferences e do Preferences DataStore é que não há como definir um esquema ou garantir que as chaves sejam acessadas com o tipo correto. O Proto DataStore resolve esse problema usando buffers de protocolo para definir o esquema. Com o uso de arquivos proto, o DataStore sabe que tipos estão armazenados e somente os fornecerá, eliminando a necessidade de usar chaves.
Veja como adicionar o Proto DataStore e Protobufs ao projeto, o que são buffers de protocolo e como usá-los com o Proto DataStore e como migrar de SharedPreferences para o DataStore.
Como adicionar dependências
Para trabalhar com o Proto DataStore e fazer com que o Protobuf gere o código para nosso esquema, será necessário fazer várias mudanças no arquivo build.gradle:
- Adicione o plug-in Protobuf.
- Adicione as dependências do Protobuf e do Proto DataStore.
- Configure o Protobuf.
plugins {
...
id "com.google.protobuf" version "0.8.17"
}
dependencies {
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
6. Como definir e usar objetos protobuf
Buffers de protocolo são um mecanismo de serialização de dados estruturados. Você define como quer que os dados sejam estruturados uma vez e, em seguida, o compilador gera um código-fonte para gravar e ler os dados estruturados com facilidade.
Criar o arquivo proto
O esquema é definido em um arquivo proto. No nosso codelab, temos duas preferências de usuário: show_completed
e sort_order
. Elas são representadas como dois objetos diferentes. Um dos nossos objetivos é unificar as duas sinalizações em uma classe UserPreferences
que é armazenada no DataStore. Em vez de definir essa classe em Kotlin, ela será definida com o esquema protobuf.
Confira o Guia da linguagem proto para ver informações detalhadas sobre a sintaxe. Neste codelab, vamos nos concentrar apenas nos tipos de que precisamos.
Crie um novo arquivo chamado user_prefs.proto
no diretório app/src/main/proto
. Se essa estrutura de pastas não for exibida, alterne para a Visualização Project. Em protobufs, cada estrutura é definida usando uma palavra-chave message
e cada membro da estrutura é definido dentro da mensagem, com base no tipo e no nome, e é atribuída uma ordem baseada em 1. Vamos definir uma mensagem UserPreferences
que, por enquanto, tem apenas um valor booleano chamado show_completed
.
syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message UserPreferences {
// filter for showing / hiding completed tasks
bool show_completed = 1;
}
Criar o serializador
Para informar ao DataStore como ler e gravar o tipo de dados que definimos no arquivo proto, precisamos implementar um serializador. O serializador também define o valor padrão a ser retornado se não houver dados no disco. Crie um novo arquivo com o nome UserPreferencesSerializer
no pacote data
:
object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserPreferences {
try {
return UserPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
7. Como persistir os dados no Proto DataStore
Como criar o DataStore
A sinalização showCompleted
é mantida na memória, no TasksViewModel
, mas precisa ser armazenada no UserPreferencesRepository
em uma instância DataStore.
Para criar uma instância DataStore, usamos a delegação dataStore
com o Context
como receptor. A delegação tem dois parâmetros obrigatórios:
- O nome do arquivo em que o DataStore vai atuar.
- O serializador para o tipo usado com o DataStore. No nosso caso:
UserPreferencesSerializer
.
Para simplificar, neste codelab, vamos fazer isso em TasksActivity
:
private const val USER_PREFERENCES_NAME = "user_preferences"
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"
private const val SORT_ORDER_KEY = "sort_order"
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = UserPreferencesSerializer
)
A delegação dataStore
garante que tenhamos uma única instância do DataStore com esse nome no aplicativo. Atualmente, UserPreferencesRepository
é implementado como um Singleton, porque ele contém o sortOrderFlow
e evita que ele seja vinculado ao ciclo de vida da TasksActivity
. Como o UserPreferenceRepository
funciona somente com os dados do DataStore e não cria ou contém nenhum objeto novo, já podemos remover a implementação do Singleton:
- Remova o
companion object
. - Torne o
constructor
público.
O UserPreferencesRepository
precisa receber uma instância DataStore
como parâmetro de construtor. Por enquanto, podemos deixar Context
como um parâmetro por ser necessário para o SharedPreferences, mas ele vai ser removido mais tarde.
class UserPreferencesRepository(
private val userPreferencesStore: DataStore<UserPreferences>,
context: Context
) { ... }
Vamos atualizar a construção do UserPreferencesRepository
na TasksActivity
e transmitir o dataStore
:
viewModel = ViewModelProvider(
this,
TasksViewModelFactory(
TasksRepository,
UserPreferencesRepository(dataStore, this)
)
).get(TasksViewModel::class.java)
Como ler dados do Proto DataStore
O Proto DataStore expõe os dados armazenados em um Flow<UserPreferences>
. Vamos criar um valor userPreferencesFlow: Flow<UserPreferences>
público que é atribuído a dataStore.data
:
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
Como gerenciar exceções ao ler dados
À medida que o DataStore lê dados de um arquivo, IOException
s são geradas quando ocorre um erro ao ler os dados. Podemos lidar com elas usando a transformação do fluxo catch
(link em inglês) e simplesmente registrando o erro:
private val TAG: String = "UserPreferencesRepo"
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
Log.e(TAG, "Error reading sort order preferences.", exception)
emit(UserPreferences.getDefaultInstance())
} else {
throw exception
}
}
Como gravar dados no Proto DataStore
Para gravar dados, o DataStore oferece uma função de suspensão DataStore.updateData()
, em que o parâmetro é o estado atual de UserPreferences
. Para atualizá-lo, teremos que transformar o objeto de preferências em um builder, definir o novo valor e depois criar as novas preferências.
updateData()
atualiza os dados de maneira transacional em uma operação atômica de leitura-gravação-modificação. A corrotina vai ser concluída quando os dados forem armazenados no disco.
Vamos criar uma função de suspensão que nos permita atualizar a propriedade showCompleted
de UserPreferences
, com o nome updateShowCompleted()
, que chama dataStore.updateData()
e define o novo valor:
suspend fun updateShowCompleted(completed: Boolean) {
dataStore.updateData { preferences ->
preferences.toBuilder().setShowCompleted(completed).build()
}
}
Nesse ponto, o app vai ser compilado, mas a funcionalidade que acabamos de criar em UserPreferencesRepository
não é usada.
8. SharedPreferences para Proto DataStore
Como definir os dados a serem salvos em proto
A ordem de classificação é salva em SharedPreferences. Vamos migrá-la para o DataStore. Para fazer isso, vamos começar atualizando as UserPreferences
no arquivo proto para que ele também armazene a ordem de classificação. Como a SortOrder
é uma enum
, é necessário a definir em UserPreference
. As enums
são definidas em protobufs de maneira semelhante ao Kotlin.
O valor padrão para enumerações é o primeiro listado na definição de tipo. No entanto, ao migrar de SharedPreferences, precisamos saber se o valor recebido é o valor padrão ou o definido anteriormente em SharedPreferences. Sendo assim, definimos um novo valor para nossa enumeração SortOrder
: UNSPECIFIED
e o listamos primeiro de modo que ele seja o valor padrão.
Nosso arquivo user_prefs.proto
vai ficar assim:
syntax = "proto3";
option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;
message UserPreferences {
// filter for showing / hiding completed tasks
bool show_completed = 1;
// defines tasks sorting order: no order, by deadline, by priority, by deadline and priority
enum SortOrder {
UNSPECIFIED = 0;
NONE = 1;
BY_DEADLINE = 2;
BY_PRIORITY = 3;
BY_DEADLINE_AND_PRIORITY = 4;
}
// user selected tasks sorting order
SortOrder sort_order = 2;
}
Limpe e recrie seu projeto para garantir que um novo objeto UserPreferences
seja gerado com o novo campo.
Agora que a SortOrder
está definida no arquivo proto, é possível remover a declaração de UserPreferencesRepository
. Excluir:
enum class SortOrder {
NONE,
BY_DEADLINE,
BY_PRIORITY,
BY_DEADLINE_AND_PRIORITY
}
Confira se a importação correta da SortOrder
está sendo usada em todos os lugares:
import com.codelab.android.datastore.UserPreferences.SortOrder
No TasksViewModel.filterSortTasks()
, estamos fazendo ações diferentes com base no tipo SortOrder
. Agora que também adicionamos a opção UNSPECIFIED
, precisamos adicionar outro caso à instrução when(sortOrder)
. Como não queremos lidar com outras opções no momento, podemos simplesmente gerar uma UnsupportedOperationException
para outros casos.
Nossa função filterSortTasks()
estará assim agora:
private fun filterSortTasks(
tasks: List<Task>,
showCompleted: Boolean,
sortOrder: SortOrder
): List<Task> {
// filter the tasks
val filteredTasks = if (showCompleted) {
tasks
} else {
tasks.filter { !it.completed }
}
// sort the tasks
return when (sortOrder) {
SortOrder.UNSPECIFIED -> filteredTasks
SortOrder.NONE -> filteredTasks
SortOrder.BY_DEADLINE -> filteredTasks.sortedByDescending { it.deadline }
SortOrder.BY_PRIORITY -> filteredTasks.sortedBy { it.priority }
SortOrder.BY_DEADLINE_AND_PRIORITY -> filteredTasks.sortedWith(
compareByDescending<Task> { it.deadline }.thenBy { it.priority }
)
// We shouldn't get any other values
else -> throw UnsupportedOperationException("$sortOrder not supported")
}
}
Como migrar de SharedPreferences
Para ajudar na migração, o DataStore define a classe SharedPreferencesMigration
. O método by dataStore
que cria o DataStore, usado em TasksActivity
, também expõe um parâmetro produceMigrations
. Neste bloco, criamos a lista das DataMigration
s que precisam ser executadas para essa instância do DataStore. No nosso caso, temos apenas uma migração: a SharedPreferencesMigration
.
Ao implementar uma SharedPreferencesMigration
, o bloco migrate
oferece dois parâmetros:
SharedPreferencesView
que nos permite extrair dados de SharedPreferences- Dados atuais de
UserPreferences
Será necessário retornar um objeto UserPreferences
.
Ao implementar o bloco migrate
, será necessário executar as seguintes etapas:
- Verifique o valor
sortOrder
emUserPreferences
. - Se for
SortOrder.UNSPECIFIED
, significa que precisamos recuperar o valor de SharedPreferences. Se não encontrar aSortOrder
, poderemos usarSortOrder.NONE
como padrão. - Depois de recebermos a ordem de classificação, será necessário converter o objeto
UserPreferences
em um builder, definir a ordem de classificação e, em seguida, criar o objeto novamente chamandobuild()
. Nenhum outro campo será afetado por essa mudança. - Se o valor
sortOrder
nasUserPreferences
não forSortOrder.UNSPECIFIED
, será possível retornar os dados atuais que recebemos emmigrate
, porque a migração já terá sido executada.
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = UserPreferencesSerializer,
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(
context,
USER_PREFERENCES_NAME
) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
// Define the mapping from SharedPreferences to UserPreferences
if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
currentData.toBuilder().setSortOrder(
SortOrder.valueOf(
sharedPrefs.getString(SORT_ORDER_KEY, SortOrder.NONE.name)!!
)
).build()
} else {
currentData
}
}
)
}
)
Agora que definimos a lógica de migração, precisamos fazer que o DataStore a use. Para isso, atualize o builder do DataStore e atribua ao parâmetro migrations
uma nova lista que contenha uma instância de SharedPreferencesMigration
:
private val dataStore: DataStore<UserPreferences> = context.createDataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer,
migrations = listOf(sharedPrefsMigration)
)
Como salvar a ordem de classificação no DataStore
Para atualizar a ordem de classificação quando enableSortByDeadline()
e enableSortByPriority()
são chamados, precisamos fazer o seguinte:
- Chame as respectivas funcionalidades no lambda de
dataStore.updateData()
. - Como
updateData()
é uma função de suspensão,enableSortByDeadline()
eenableSortByPriority()
também precisam ser transformadas em funções de suspensão. - Use as
UserPreferences
atuais recebidas deupdateData()
para criar a nova ordem de classificação. - Atualize as
UserPreferences
convertendo-as para builder, depois defina a nova ordem de classificação e crie as preferências novamente.
Veja como é a implementação de enableSortByDeadline()
. Você vai poder fazer as mudanças em enableSortByPriority()
por conta própria.
suspend fun enableSortByDeadline(enable: Boolean) {
// updateData handles data transactionally, ensuring that if the sort is updated at the same
// time from another thread, we won't have conflicts
dataStore.updateData { preferences ->
val currentOrder = preferences.sortOrder
val newSortOrder =
if (enable) {
if (currentOrder == SortOrder.BY_PRIORITY) {
SortOrder.BY_DEADLINE_AND_PRIORITY
} else {
SortOrder.BY_DEADLINE
}
} else {
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
SortOrder.BY_PRIORITY
} else {
SortOrder.NONE
}
}
preferences.toBuilder().setSortOrder(newSortOrder).build()
}
}
Agora é possível remover o parâmetro de construtor context
e todos os usos de SharedPreferences.
9. Atualizar TasksViewModel para usar UserPreferencesRepository
Agora que o UserPreferencesRepository
armazena as sinalizações show_completed
e sort_order
no DataStore e expõe um Flow<UserPreferences>
, vamos atualizar o TasksViewModel
para usar esses elementos.
Remova showCompletedFlow
e sortOrderFlow
. Crie um valor com o nome userPreferencesFlow
que vai ser inicializado com userPreferencesRepository.userPreferencesFlow
:
private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow
Na criação do tasksUiModelFlow
, substitua showCompletedFlow
e sortOrderFlow
por userPreferencesFlow
. Substitua os parâmetros corretamente.
Ao chamar filterSortTasks
, transmita showCompleted
e sortOrder
das userPreferences
. O código ficará assim:
private val tasksUiModelFlow = combine(
repository.tasks,
userPreferencesFlow
) { tasks: List<Task>, userPreferences: UserPreferences ->
return@combine TasksUiModel(
tasks = filterSortTasks(
tasks,
userPreferences.showCompleted,
userPreferences.sortOrder
),
showCompleted = userPreferences.showCompleted,
sortOrder = userPreferences.sortOrder
)
}
A função showCompletedTasks()
agora precisa ser atualizada para chamar userPreferencesRepository.updateShowCompleted()
. Como esta é uma função de suspensão, crie uma nova corrotina no viewModelScope
:
fun showCompletedTasks(show: Boolean) {
viewModelScope.launch {
userPreferencesRepository.updateShowCompleted(show)
}
}
As funções de userPreferencesRepository
, enableSortByDeadline()
e enableSortByPriority()
agora são de suspensão. Portanto, também precisam ser chamadas em uma nova corrotina, iniciada no viewModelScope
:
fun enableSortByDeadline(enable: Boolean) {
viewModelScope.launch {
userPreferencesRepository.enableSortByDeadline(enable)
}
}
fun enableSortByPriority(enable: Boolean) {
viewModelScope.launch {
userPreferencesRepository.enableSortByPriority(enable)
}
}
Limpar UserPreferencesRepository
Vamos remover os campos e métodos que não são mais necessários. Você poderá excluir o seguinte:
_sortOrderFlow
sortOrderFlow
updateSortOrder()
private val sortOrder: SortOrder
private val sharedPreferences
Agora, o app vai ser compilado corretamente. Execute o app para ver se as sinalizações show_completed
e sort_order
foram salvas corretamente.
Confira a ramificação proto_datastore
do repositório do codelab para comparar as mudanças.
10. Resumo
Agora que você migrou para o Proto DataStore, vamos recapitular o que aprendemos:
- SharedPreferences vem com uma série de desvantagens. Uma API síncrona que pode parecer segura para chamadas na linha de execução de IU, nenhum mecanismo de sinalização de erros, a falta da API transacional, entre outras.
- O DataStore é um substituto de SharedPreferences, sendo a solução para a maioria das limitações da API.
- O DataStore tem uma API totalmente assíncrona que usa corrotinas e fluxos Kotlin, gerencia a migração de dados, garante a consistência de dados e lida com casos de corrupção deles.