---
title: "DataStore Preferences com Kotlin: Guia Android em 2026 | Kotlin Brasil"
url: "https://kotlin.dev.br/tutoriais/datastore-preferences-kotlin/"
markdown_url: "https://kotlin.dev.br/tutoriais/datastore-preferences-kotlin.MD"
description: "Aprenda DataStore Preferences com Kotlin no Android: setup, chaves tipadas, Flow, Repository, migrations de SharedPreferences e boas práticas."
date: "2026-05-23"
author: "Karina Melo"
---

# DataStore Preferences com Kotlin: Guia Android em 2026 | Kotlin Brasil

Aprenda DataStore Preferences com Kotlin no Android: setup, chaves tipadas, Flow, Repository, migrations de SharedPreferences e boas práticas.


DataStore Preferences é a alternativa moderna ao `SharedPreferences` para salvar configurações simples em apps Android com Kotlin. Ele resolve problemas comuns de leitura bloqueante, concorrência, callbacks difíceis de testar e integração fraca com arquitetura reativa. Em vez de chamar `getString()` espalhado pela aplicação, você expõe um `Flow` tipado, grava de forma assíncrona e mantém a camada de UI observando estado consistente.

O caso de uso ideal é pequeno e frequente: tema escolhido, filtros de lista, flags de onboarding, último usuário selecionado, timestamp da última sincronização, preferências de notificação ou toggles de recurso. Para dados relacionais, listas grandes, busca, paginação ou histórico, use [Room com Kotlin](/tutoriais/kotlin-room-database-tutorial/). Para sincronização confiável em background, combine com [WorkManager](/blog/workmanager-kotlin-android-2026/). DataStore entra no meio: simples como preferências, mas com ergonomia de Kotlin moderno.

## Quando usar DataStore Preferences?

Use DataStore Preferences quando você precisa persistir pares chave-valor pequenos e observar mudanças no app inteiro. Ele é especialmente útil quando a preferência afeta várias telas, porque o `Flow` permite que ViewModels reajam automaticamente.

Bons exemplos:

- tema claro, escuro ou seguindo o sistema;
- idioma selecionado dentro do app;
- filtro padrão de uma lista;
- ordenação escolhida pelo usuário;
- flag de onboarding concluído;
- token de paginação simples;
- data da última sincronização bem-sucedida;
- preferências locais de notificações.

Evite DataStore para objetos grandes ou coleções que precisam de consulta. Se você quer filtrar tarefas por status, buscar por texto, relacionar entidades ou migrar esquema com várias tabelas, a escolha correta é Room. DataStore não é um banco de dados; ele é uma camada segura para preferências pequenas.

## DataStore Preferences vs Proto DataStore

O Jetpack oferece duas formas principais de DataStore:

| Opção | Melhor para | Trade-off |
|---|---|---|
| Preferences DataStore | chaves simples, setup rápido, migração de SharedPreferences | não tem schema formal |
| Proto DataStore | modelo tipado com schema e evolução controlada | exige protobuf e mais configuração |

Para a maioria dos apps Android iniciando uma tela de configurações, Preferences DataStore é suficiente. Proto DataStore faz mais sentido quando a configuração vira um objeto de domínio importante, precisa de validação estrutural ou será compartilhada entre módulos com contrato explícito.

Neste guia vamos focar em Preferences DataStore, porque ele cobre o caminho mais comum para substituir `SharedPreferences` sem criar complexidade prematura.

## Dependências

Adicione a dependência no módulo Android:

```kotlin
dependencies {
    implementation("androidx.datastore:datastore-preferences:1.1.1")
}
```

Se o projeto já usa Compose, Coroutines e Flow, nenhuma mudança conceitual grande é necessária. DataStore combina naturalmente com ViewModel e `collectAsStateWithLifecycle`.

## Criando o DataStore

O padrão mais comum é criar uma extensão no `Context` usando o delegate `preferencesDataStore`:

```kotlin
import android.content.Context
import androidx.datastore.preferences.preferencesDataStore

private val Context.userPreferencesDataStore by preferencesDataStore(
    name = "user_preferences",
)
```

O nome deve ser estável. Trocar esse nome cria outro arquivo de preferências, então trate como parte da persistência do app. Em projetos grandes, mantenha a extensão em um módulo de dados ou infraestrutura, não dentro de uma Activity.

## Modelando preferências tipadas

Mesmo usando Preferences DataStore, evite espalhar strings de chave pela aplicação. Defina chaves em um único lugar e exponha um modelo de domínio simples:

```kotlin
enum class TemaApp {
    Sistema,
    Claro,
    Escuro,
}

data class UserPreferences(
    val tema: TemaApp = TemaApp.Sistema,
    val onboardingConcluido: Boolean = false,
    val ordemFavoritos: String = "recentes",
)
```

Agora crie as chaves:

```kotlin
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey

private object PreferenceKeys {
    val Tema = stringPreferencesKey("tema")
    val OnboardingConcluido = booleanPreferencesKey("onboarding_concluido")
    val OrdemFavoritos = stringPreferencesKey("ordem_favoritos")
}
```

Essa pequena organização evita bugs bobos como usar `theme` em uma tela e `tema` em outra. Também deixa migrações e testes mais simples.

## Repository para leitura com Flow

Crie uma classe responsável por transformar `Preferences` em um modelo da aplicação:

```kotlin
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException

class UserPreferencesRepository(
    private val dataStore: DataStore<Preferences>,
) {
    val preferences: Flow<UserPreferences> = dataStore.data
        .catch { exception ->
            if (exception is IOException) {
                emit(androidx.datastore.preferences.core.emptyPreferences())
            } else {
                throw exception
            }
        }
        .map { prefs ->
            UserPreferences(
                tema = prefs[PreferenceKeys.Tema]
                    ?.let { runCatching { TemaApp.valueOf(it) }.getOrNull() }
                    ?: TemaApp.Sistema,
                onboardingConcluido = prefs[PreferenceKeys.OnboardingConcluido] ?: false,
                ordemFavoritos = prefs[PreferenceKeys.OrdemFavoritos] ?: "recentes",
            )
        }
}
```

O `catch` acima trata erro de leitura do arquivo como fallback para preferências vazias. Não engula qualquer exceção. Se o problema for programação incorreta, corrupção não tratada ou outro erro inesperado, deixar a exceção aparecer ajuda a corrigir o bug.

## Gravando preferências

Para escrever, use `edit`. A operação é suspensa e transacional para o arquivo de preferências:

```kotlin
import androidx.datastore.preferences.core.edit

suspend fun definirTema(tema: TemaApp) {
    dataStore.edit { prefs ->
        prefs[PreferenceKeys.Tema] = tema.name
    }
}

suspend fun concluirOnboarding() {
    dataStore.edit { prefs ->
        prefs[PreferenceKeys.OnboardingConcluido] = true
    }
}

suspend fun definirOrdemFavoritos(ordem: String) {
    dataStore.edit { prefs ->
        prefs[PreferenceKeys.OrdemFavoritos] = ordem
    }
}
```

Não faça escrita direta na UI. A tela chama um método do ViewModel, o ViewModel chama o repository e o estado volta pelo `Flow`. Esse fluxo unidirecional evita inconsistência entre “o botão foi tocado” e “a preferência realmente foi persistida”.

## Usando no ViewModel

No ViewModel, exponha o fluxo como estado de tela:

```kotlin
class SettingsViewModel(
    private val repository: UserPreferencesRepository,
) : ViewModel() {

    val uiState: StateFlow<UserPreferences> = repository.preferences
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = UserPreferences(),
        )

    fun selecionarTema(tema: TemaApp) {
        viewModelScope.launch {
            repository.definirTema(tema)
        }
    }
}
```

Se você já usa [MVVM com Kotlin](/tutoriais/kotlin-mvvm-tutorial/), esse desenho deve parecer familiar: a UI observa estado, o ViewModel expõe ações e o repository cuida da persistência.

## Consumindo em Compose

Em Jetpack Compose, a tela pode observar o estado com lifecycle-aware collection:

```kotlin
@Composable
fun SettingsScreen(
    viewModel: SettingsViewModel,
) {
    val preferences by viewModel.uiState.collectAsStateWithLifecycle()

    Column {
        Text("Tema")

        TemaApp.entries.forEach { tema ->
            Row(
                modifier = Modifier.clickable {
                    viewModel.selecionarTema(tema)
                },
            ) {
                RadioButton(
                    selected = preferences.tema == tema,
                    onClick = { viewModel.selecionarTema(tema) },
                )
                Text(text = tema.name)
            }
        }
    }
}
```

Para aprofundar a camada visual, veja também o guia de [Jetpack Compose](/guias/guia-jetpack-compose/) e os tutoriais de layouts. DataStore não deve conhecer Compose; ele apenas fornece estado.

## Migração de SharedPreferences

Se o app já usa `SharedPreferences`, DataStore oferece migração. Isso é importante porque trocar a implementação sem migrar pode resetar tema, filtros e flags do usuário.

```kotlin
private val Context.userPreferencesDataStore by preferencesDataStore(
    name = "user_preferences",
    produceMigrations = { context ->
        listOf(
            SharedPreferencesMigration(
                context = context,
                sharedPreferencesName = "legacy_user_preferences",
            ),
        )
    },
)
```

Antes de remover o código antigo, valide em um build com dados reais ou fixture de QA. A migração deve preservar os nomes das chaves relevantes ou mapear valores quando o formato mudou. Se antes você salvava `"dark"` e agora usa `TemaApp.Escuro.name`, trate essa conversão explicitamente.

## DataStore em arquitetura offline-first

Em apps offline-first, DataStore costuma guardar metadados pequenos enquanto Room guarda o estado principal. Um exemplo comum:

- Room: entidades, filas pendentes, dados exibidos na tela;
- DataStore: último sync bem-sucedido, filtro escolhido, usuário ativo, feature flag local;
- WorkManager: execução de sincronização quando houver rede;
- Repository: regra que combina tudo isso.

Esse desenho evita dois extremos ruins: colocar preferências simples no banco só por formalidade ou enfiar dados de produto dentro de DataStore porque é mais rápido de começar. Cada ferramenta resolve um problema.

Se a sua tela offline-first precisa mostrar “última atualização: 10:32”, DataStore pode guardar esse timestamp. Se precisa listar 300 pedidos pendentes, use Room.

## Testes

O repository de DataStore é fácil de testar quando você injeta `DataStore<Preferences>` em vez de buscar `Context` diretamente dentro da classe. Em testes instrumentados ou Robolectric, crie um arquivo temporário e valide leitura/escrita.

O que vale testar:

- valor padrão quando não existe preferência salva;
- escrita de tema atualiza o fluxo observado;
- valores antigos de SharedPreferences são migrados;
- enum inválido cai para valor seguro;
- erro de leitura esperado não derruba a tela;
- ViewModel chama o repository em uma ação de UI.

Para testes unitários de lógica ao redor, combine com [JUnit 5 e MockK](/blog/kotlin-testes-junit5-mockk-guia/). Para fluxos, Turbine também é uma boa ferramenta, especialmente quando você precisa validar sequência de emissões.

## Erros comuns

O primeiro erro é usar DataStore como banco. Quando preferências começam a virar JSON grande, lista serializada ou mapa complexo, provavelmente você está escondendo uma entidade que deveria estar no Room.

O segundo erro é ler DataStore de forma pontual em todo lugar. O modelo idiomático é observar `Flow` e transformar em estado. Se cada tela cria sua própria leitura avulsa, o app fica difícil de testar.

O terceiro erro é salvar strings livres sem fallback. Enums mudam, valores antigos continuam no dispositivo e usuários atualizam de versões antigas. Sempre trate valor desconhecido.

O quarto erro é esquecer que preferências também fazem parte da experiência. Resetar tema, idioma, onboarding ou filtros em uma atualização passa sensação de app quebrado. Migração deve entrar no checklist de release.

## Checklist prático

Antes de usar DataStore Preferences em produção, confirme:

- as chaves ficam centralizadas;
- o repository expõe um modelo tipado;
- a UI observa estado via ViewModel;
- valores padrão são explícitos;
- enums têm fallback seguro;
- SharedPreferences legado foi migrado quando necessário;
- dados grandes continuam no Room;
- testes cobrem leitura, escrita e migração.

## Conclusão

DataStore Preferences é uma melhoria direta para apps Android Kotlin que ainda dependem de `SharedPreferences`. Ele combina persistência simples, coroutines, Flow e uma API mais segura para arquitetura moderna. O ganho não é apenas técnico: preferências reativas deixam a UI consistente, reduzem leituras bloqueantes e tornam testes mais previsíveis.

Use DataStore para configurações pequenas, Room para dados estruturados e WorkManager para trabalho persistente em background. Essa combinação forma uma base forte para apps Android reais em 2026: responsivos, offline-aware e fáceis de evoluir. Para quem está montando portfólio, uma tela de configurações com DataStore bem organizado mostra maturidade além do CRUD básico.
