DataStore é um componente da biblioteca Jetpack projetado para armazenar pequenas quantidades de dados em aplicativos Android. Ao contrário do SharedPreferences, ele funciona de forma assíncrona e garante a consistência dos dados sob acesso concorrente. De acordo com Google, 2024, o DataStore usa Kotlin Coroutines e Flow, tornando-o seguro para a thread principal e adequado para arquiteturas reativas.
Principais Pontos
DataStore é uma solução da Google para armazenamento local de dados no Android, apresentada em 2020 como alternativa ao SharedPreferences. Suporta dois modos: Preferences DataStore (pares simples chave-valor) e Proto DataStore (esquema tipado baseado em Protocol Buffers).
A principal vantagem é a assincronia total: todas as operações de leitura retornam um Flow do Kotlin Coroutines, e as escritas são realizadas em um contexto de corrotina. Isso elimina o bloqueio da thread principal, que era um problema típico do SharedPreferences ao lidar com grandes volumes de dados.
O DataStore garante a atomicidade das operações: escritas concorrentes não causam perda de dados graças ao seu modelo transacional. Se dois componentes modificarem o mesmo valor simultaneamente, o DataStore lida corretamente com o conflito através de um mecanismo compare-and-swap.
De acordo com o Google I/O 2023, o DataStore é usado em 40% dos novos projetos Android, e o Google recomenda migrar do SharedPreferences em todos os aplicativos onde a estabilidade do armazenamento de configurações é necessária.
No núcleo do DataStore está o SingleProcessDataStore — uma implementação que opera dentro de um único processo. Ele usa armazenamento baseado em arquivos com bloqueio em nível de arquivo: ao escrever dados, o arquivo é bloqueado, prevenindo corrupção sob acesso concorrente.
O DataStore lida automaticamente com erros de desserialização: se o arquivo estiver corrompido, ele retorna um valor padrão e sobrescreve o arquivo. Este comportamento é configurável através do corruptionHandler, que pode ser definido ao criar o DataStore.
SharedPreferences sofre de três problemas fundamentais: leitura síncrona do disco na thread principal, falta de garantias de atomicidade para escritas concorrentes e impossibilidade de rastrear alterações de forma reativa. O DataStore resolve todos os três: Flow para observação, bloqueio de arquivos para atomicidade e API assíncrona para segurança de threads.
DataStore armazena dados em arquivos no armazenamento interno do dispositivo. O Preferences DataStore usa um formato de arquivo semelhante ao SharedPreferences, mas com metadados adicionais para verificação de integridade. O Proto DataStore usa o formato binário Protocol Buffers, o que reduz o tamanho do arquivo e acelera a serialização.
Ao ler dados, o DataStore carrega o arquivo inteiro na memória uma vez, após o que os assinantes recebem o estado atual através do Flow. As alterações são transmitidas a todos os assinantes ativos automaticamente — não é necessário registro manual de listeners, como no SharedPreferences.
Preferences DataStore usa um mecanismo de serialização integrado baseado em um Map. Cada entrada é um par de uma string e um tipo primitivo (Int, Boolean, Float, Long, String, Set). Os dados são armazenados em um arquivo XML, semelhante ao SharedPreferences, mas com escrita atômica através de bloqueio de arquivo.
Exemplo de criação do Preferences DataStore: a extensão preferencesDataStore no Context cria um singleton com o nome do arquivo. Em chamadas repetidas, a mesma instância é retornada — isso elimina a duplicação de arquivos e confusão entre diferentes instâncias de armazenamento.
Proto DataStore requer a definição de um esquema de dados através de um arquivo .proto e compilação com o plugin protobuf. A classe Java gerada é usada como o único ponto de entrada para todos os campos — isso elimina erros de digitação nas chaves, comuns no SharedPreferences.
O esquema do Proto DataStore é definido uma vez e suporta a adição de novos campos sem perder dados antigos. Se uma nova versão do aplicativo adicionar um campo com um valor padrão, o arquivo antigo será desserializado corretamente — a compatibilidade reversa está incorporada no protocolo.
A escolha entre Preferences DataStore e Proto DataStore depende da complexidade dos dados e dos requisitos de tipificação. Ambas as opções são assíncronas e transacionais, mas diferem em segurança de tipos e desempenho de serialização.
| Característica | Preferences DataStore | Proto DataStore |
|---|---|---|
| Tipificação | Fraca (chave-valor) | Forte (classe gerada) |
| Serialização | XML (integrada) | Protocol Buffers (protobuf) |
| Tamanho do arquivo | Grande (XML legível) | Pequeno (binário) |
| Complexidade | Baixa (sem .proto) | Média (requer .proto) |
| Migração de esquema | Sem esquema | Automática (proto) |
| Compatibilidade | SharedPreferences (via migração) | Apenas Proto DataStore |
Preferences DataStore é adequado para configurações simples: flags de recursos, string de token de autorização, contagem de inicializações do aplicativo. Se os dados são poucos (até 10–15 chaves) e não requerem um esquema estrito, o Preferences DataStore fornece um limiar mínimo de entrada sem conectar o plugin protobuf.
Proto DataStore é justificado quando a estrutura de dados é complexa ou pode mudar entre versões do aplicativo. Por exemplo, configurações de perfil de usuário ou configuração de testes A/B com 20+ campos. O Protobuf fornece tipificação forte e migrações automáticas, eliminando erros de tempo de execução devido a incompatibilidade de chaves.
Google fornece um mecanismo de migração integrado através da classe SharedPreferencesMigration. A migração é realizada uma vez na primeira inicialização após a atualização do aplicativo: o DataStore lê os dados do SharedPreferences, escreve em seu próprio formato e marca a migração como concluída.
A migração suporta transformações personalizadas: se as chaves no SharedPreferences não corresponderem às chaves desejadas do DataStore, você pode especificar uma função de transformação através do SharedPreferencesMigration. Isso permite renomear chaves e alterar tipos de dados durante a migração.
Primeiro, adicione o DataStore ao build.gradle e crie uma instância do DataStore com migração: SharedPreferencesMigration aceita o nome do arquivo SharedPreferences e um conjunto de chaves a serem transferidas. Segundo, remova todo o código que funciona através do SharedPreferences e substitua por chamadas do DataStore. Terceiro, teste a migração: na primeira inicialização, os dados devem aparecer no DataStore e o arquivo SharedPreferences antigo não deve mais ser usado.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore integra-se facilmente a um projeto existente. Abaixo estão exemplos práticos para Preferences DataStore e Proto DataStore — ambos demonstram leitura, escrita e observação reativa de dados.
Neste exemplo, o Preferences DataStore armazena três configurações: tema escuro, nome de usuário e contagem de inicializações. A leitura é feita através da extensão .data, que retorna um Flow. A escrita é feita através da função suspend .edit, que garante a atomicidade das alterações.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore requer a definição de um arquivo .proto. Após a compilação, uma classe UserSettings é criada e usada para leitura e escrita. As migrações de versão do esquema são descritas no mesmo arquivo .proto e aplicadas automaticamente.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Leitura do DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Escrita de novos valores
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore integra-se com a arquitetura MVVM através do ViewModel. O Flow do DataStore é coletado através do .stateIn e usado na UI. A cada alteração de dados, a UI é atualizada automaticamente — não são necessárias atualizações manuais ou LiveData.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
Perguntas Frequentes
DataStore funciona de forma assíncrona (não bloqueia a thread de UI), suporta acesso concorrente através de transações e permite subscrição reativa a mudanças via Flow. SharedPreferences é uma API síncrona com risco de ANR com grandes volumes de dados e sem suporte reativo integrado.
DataStore é escrito em Kotlin e requer Kotlin Coroutines. Usá-lo a partir de Java é possível, mas inconveniente: seria necessário criar wrappers com CompletableFuture ou gerenciar corrotinas manualmente. Para projetos Java, o Google recomenda manter o SharedPreferences ou adicionar Kotlin ao módulo.
DataStore carrega o arquivo inteiro na memória ao ler, portanto não é adequado para armazenar listas ou objetos grandes. Para tais cenários, use Room ou SQLite. O DataStore é otimizado para configurações e dados estruturados pequenos — até centenas de kilobytes.
Ao criar um DataStore, você pode passar um corruptionHandler — uma função chamada quando o arquivo está corrompido. Por padrão, o DataStore lança uma exceção CorruptionException. No corruptionHandler, você pode retornar dados vazios, após o que o DataStore sobrescreverá o arquivo com um estado correto.
Sim, o Proto DataStore requer a definição de um esquema em um arquivo .proto e a conexão do protobuf-gradle-plugin. Se o projeto é pequeno e os dados são simples, é mais fácil usar o Preferences DataStore — ele não requer configuração adicional de compilação.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também