Exemplos de Código: Capítulo 14 — Armazenamento com Jetpack DataStore

Especialização em Mobile Nativo com Android • Demonstrações Práticas Compiláveis


💻 Código de Demonstração: Preferences DataStore, Chaves Fortemente Tipadas e Leitura Reativa

O código abaixo demonstra a substituição moderna e segura do legado SharedPreferences pela biblioteca Jetpack DataStore:

  1. Leitura não-bloqueante através de Kotlin Coroutines e Flow.
  2. Garantia de consistência transacional e isolamento contra corrupção de arquivos via dataStore.edit {}.
  3. Tipagem estrita de chaves com stringPreferencesKey, booleanPreferencesKey e intPreferencesKey.
package br.com.portal.mobile.android.cap14

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking

// =====================================================================
// 1. Configuração e Chaves do DataStore (Contratos de Preferência)
// =====================================================================
/*
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import java.io.IOException

// Extensão delegada criando uma única instância por processo
val Context.configDataStore: DataStore<Preferences> by preferencesDataStore(name = "portal_config")
*/

data class ConfiguracoesUsuario(
    val modoEscuroAtivo: Boolean,
    val tokenAutenticacao: String?,
    val idiomaSelecionado: String
)

// =====================================================================
// 2. Repositório de Preferências baseado em DataStore
// =====================================================================

class ConfiguracoesRepository(
    // private val dataStore: DataStore<Preferences>
) {
    /*
    private object Chaves {
        val MODO_ESCURO = booleanPreferencesKey("chave_modo_escuro")
        val TOKEN_JWT = stringPreferencesKey("chave_token_jwt")
        val IDIOMA = stringPreferencesKey("chave_idioma")
    }

    // Leitura contínua e reativa via Flow (Lança emptyPreferences em falhas I/O)
    val configuracoesFlow: Flow<ConfiguracoesUsuario> = dataStore.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }
        .map { preferences ->
            ConfiguracoesUsuario(
                modoEscuroAtivo = preferences[Chaves.MODO_ESCURO] ?: false,
                tokenAutenticacao = preferences[Chaves.TOKEN_JWT],
                idiomaSelecionado = preferences[Chaves.IDIOMA] ?: "pt-BR"
            )
        }

    // Gravação assíncrona atômica
    suspend fun salvarModoEscuro(ativo: Boolean) {
        dataStore.edit { preferences ->
            preferences[Chaves.MODO_ESCURO] = ativo
        }
    }

    suspend fun salvarTokenAutenticacao(token: String) {
        dataStore.edit { preferences ->
            preferences[Chaves.TOKEN_JWT] = token
        }
    }

    suspend fun limparSessao() {
        dataStore.edit { preferences ->
            preferences.remove(Chaves.TOKEN_JWT)
        }
    }
    */
}

// =====================================================================
// 3. Emulador de Teste de DataStore em Memória
// =====================================================================

class FakeDataStore {
    private val dados = MutableStateFlow(
        ConfiguracoesUsuario(
            modoEscuroAtivo = false,
            tokenAutenticacao = null,
            idiomaSelecionado = "pt-BR"
        )
    )

    val streamConfiguracoes: Flow<ConfiguracoesUsuario> = dados

    suspend fun atualizarModoEscuro(ativo: Boolean) {
        dados.value = dados.value.copy(modoEscuroAtivo = ativo)
    }

    suspend fun autenticar(token: String) {
        dados.value = dados.value.copy(tokenAutenticacao = token)
    }
}

fun main() = runBlocking {
    println("=== GERENCIAMENTO DE ESTADO COM DATASTORE PREFERENCES ===")
    val fakeStorage = FakeDataStore()

    println("[1] Estado Inicial Padrão:")
    println(" -> Modo Escuro: ${fakeStorage.streamConfiguracoes.map { it.modoEscuroAtivo }}")

    // Atualização de configurações
    fakeStorage.atualizarModoEscuro(true)
    fakeStorage.autenticar("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")

    fakeStorage.streamConfiguracoes.collect { config ->
        println("\n[2] Preferências Atualizadas via Coroutine:")
        println(" -> Modo Escuro Ativo:  ${config.modoEscuroAtivo}")
        println(" -> Token Autenticado:  ${config.tokenAutenticacao?.take(20)}...")
        println(" -> Idioma do App:      ${config.idiomaSelecionado}")
        return@collect
    }
}