Preferences DataStore vs SharedPreferences | Digital Innovation One
Preferences DataStore vs SharedPreferences
Francisco Rasia06/04/2021 21:01
O Android Jetpack pretende ser um conjunto melhores práticas para a construção de apps robustos, seguros e escalonáveis.
Room, MVVM, Retrofit, Fragments são apenas algumas das tecnologias que compõem o Jetpack, e todas elas trazem grandes inovações e maneiras mais simples de se realizarem tarefas que seriam tediosas, verbosas e dependentes de bibliotecas de terceiros. Mas a migração para uma nova arquitetura nem sempre é simples, pois não apenas temos que lidar com o código legado, mas temos que encarar umbrais de aprendizado, custos de refatoração e retrabalho, sem falar nos riscos envolvidos em qualquer migração, como a indisponibilidade de serviços, quebra do app, etc.
TL;DR: 📼 Passo-a-passo em vídeo: https://youtu.be/TMGn9UjLf6g
🛸Tá! Mas onde as Shared Preferences entram nessa história?
Entre as novas tecnologias sendo impulsionadas pela Google está a persistência de dados por meio de Preference DataStore, que propõe substituir as Shared Preferences. Como se lê na página de documentação:
O Jetpack DataStore é uma solução de armazenamento de dados que permite armazenar pares de chave-valor ou objetos tipados com buffers de protocolo. O DataStore usa corrotinas e fluxo do Kotlin para armazenar dados de forma assíncrona, consistente e transacional. Se você estiver usando SharedPreferences para armazenar dados, considere migrar para o DataStore.
Mas o que significa armazenamento assíncrono? De maneira muito sintética, se trata de fazer a gravação dos dados fora da thread principal, sem travar a interface gráfica. O acesso aos dados gravados se vale dos recursos de corrotinas para fazer as operações de maneira segura e ágil, sem as complicações decorrentes do gerenciamento de threads.
Além disso, apps que adotam o framework MVVM, em que os dados são mantidos em um ViewModel têm dificuldades em acessar as Shared Preferences, pois o acesso a estas depende de receber o contexto (o famigerado objeto context) e, como está talhado em pedra no altar-mor do templo dos deuses do Android, "Não passarás o context para o ViewModel".
🚀Um quase-guia de migração
Sabe aquele projeto que começa de um jeito mas que muda completamente no meio do caminho? Bem, caro leitor ou leitora, você se encontra diante de um desses casos.
Quando comecei a preparar o guia de migração eu tinha um roteiro bem definido e uma ideia perfeita de como iria fazer. Até que, no primeiro item do roteiro, percebi que estava usando uma versão defasada de uma dependência (que, a propósito, está em versão alpha), eu não consegui importar os módulos, e acabei tendo que descobrir toda uma nova maneira de migrar para o DataStore.
(☕Vou usar como referência o app DrinkCoffee; os links para o github e outros recursos vão estar no final do artigo.)
1 - Adicionar as seguintes dependências no build.gradle (Module: app)
Essas dependências são necessárias para dar acesso ao DataStore, ao LifeCycle e às corrotinas.
2 - Criar um Preferences DataStore
A própria documentação da Google recomendar fazê-lo por delegação e como uma variável global, no cabeçalho da MainActivity.
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "coffee_counter")
Dessa maneira o dataStore fica disponível em todo o projeto, como um singleton.
3 - Criar uma função para gravar a preferência
Essa função tem o modificador suspend, que indica que pode ser interrompida a qualquer momento durante a execução.
A função recebe uma chave (key) e um valor (value, no caso, um Int). Sim, vocês adivinharam: um par chave-valor!
Mas existe um detalhe importante: a chave recebida como parâmetro não é gravada diretamente, ela precisa ser convertida em uma PreferenceKey<T>(); é essa PreferenceKey que vai ser utilizada para identificar o valor dentro das preferências (que, na verdade, são MutablePreferences...)
A função pode ser criada no corpo da própria MainActivity:
suspend fun save(key: String, value: Int) {
val dataStoreKey = preferencesKey<Int>(key)
dataStore.edit { coffeeCount ->
coffeeCount[dataStoreKey] = value
4 - Criar uma função para ler a preferência
É muito similar à função save(), mas recebe somente a chave como parâmetro. De maneira análoga, a chave precisa ser transformada em uma PreferenceKey, e então o fluxo de dados retornado do dataStore é mapeado para um fluxo de dados do tipo Int - por isso o retorno da função é um Flow<Int>, e não simplesmente o Int esperado.
fun read(key: String): Flow<Int> {
val prefKey = intPreferencesKey(key)
return dataStore.data.map { coffeCount ->
coffeCount[prefKey] ?: 0
5 - Usar corrotinas para chamar os métodos de save() e read()
Como são tarefas assíncronas, se invocam os métodos por meio de chamadas de launch { } a partir do ciclo de vida (da atividade):
cupImageview.setOnClickListener {
mViewModel.incrementCounter()
val count = mViewModel.coffeeCounter.value
save(KEY_COFFEE_COUNT, count ?: 0)
(No exemplo, a gravação de um valor é disparada pelo clique em um componente visual.)
E, no momento de ler o dado gravado, pegamos o primeiro elemento do fluxo (um Int) com a função first():
mViewModel.setCoffeCounterTo(read(KEY_COFFEE_COUNT).first())
E... voilà! Acabamos de migrar para gravação em DataStore!
A migração para o DataStore não é um processo simples; além das peculiaridades do modelo, também precisei começar a entender as corrotinas do Kotlin.
Entretanto, mesmo em uma primeira implementação, já consegui perceber que o uso do DataStore ameniza as dificuldades de compatibilização entre SharedPreferences e a manutenção de dados no ViewModel.
📼 Passo-a-passo em vídeo: https://youtu.be/TMGn9UjLf6g
📖 Documentação sobre Preferences DataStore na página oficial do Android
💻App DrinkCoffee no meu repositório do github; procure o branch `exercicio-datastore` e o commit `6e80926`