1. Antes de começar
Introdução
Nesta unidade, você aprendeu a usar o SQL e o Room para salvar dados localmente em um dispositivo. Essas ferramentas são avançadas. No entanto, nos casos em que você não precisa armazenar dados relacionais, o DataStore pode oferecer uma solução simples. O componente Jetpack DataStore é uma ótima maneira de armazenar conjuntos de dados pequenos e simples com baixa sobrecarga. O DataStore tem duas implementações diferentes: Preferences DataStore e Proto DataStore.
- O
Preferences DataStorearmazena pares de chave-valor. Os valores podem ser tipos de dados básicos do Kotlin, comoString,BooleaneInteger. Ele não armazena conjuntos de dados complexos. Ele não requer um esquema predefinido. O principal caso de uso doPreferences Datastoreé armazenar preferências do usuário no dispositivo. - O
Proto DataStorearmazena tipos de dados personalizados. Ele exige um esquema predefinido que mapeie definições proto com estruturas de objetos.
Este codelab aborda apenas o Preferences DataStore, mas você pode ler mais sobre o Proto DataStore na documentação do DataStore.
O Preferences DataStore é uma ótima maneira de armazenar configurações controladas pelo usuário. Neste codelab, você vai aprender a implementar o DataStore para fazer exatamente isso.
Pré-requisitos:
- Concluir o curso "Noções básicas do Android com o Compose" pelo codelab Ler e atualizar dados com o Room.
O que é necessário
- Um computador com acesso à Internet e o Android Studio.
- Um dispositivo ou emulador.
- O código inicial do app Dessert Release.
O que você vai criar
O app Dessert Release mostra uma lista de versões do Android. O ícone na barra de apps alterna o layout entre uma visualização em grade e uma em lista.

No estado atual, o app não salva a seleção do layout. Quando você fechar o app, a seleção do layout não vai ser salva, e a configuração voltará à seleção padrão. Neste codelab, você vai adicionar um DataStore ao app Dessert Release e usá-lo para armazenar uma preferência de seleção de layout.
2. Faça o download do código inicial
Clique no link abaixo para fazer o download de todo o código para este codelab:
Ou, se preferir, clone o código da versão do Dessert no GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-release.git $ cd basic-android-kotlin-compose-training-dessert-release $ git checkout starter
- No Android Studio, abra a pasta
basic-android-kotlin-compose-training-dessert-release. - Abra o código do app Dessert Release no Android Studio.
3. Configurar dependências
Adicione o código abaixo a dependencies no arquivo app/build.gradle.kts:
implementation("androidx.datastore:datastore-preferences:1.0.0")
4. Implementar o repositório de preferências do usuário
- No pacote
data, crie uma nova classe com o nomeUserPreferencesRepository.

- No construtor
UserPreferencesRepository, defina uma propriedade de valor particular para representar uma instância de objetoDataStorecom um tipoPreferences.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
}
O DataStore armazena pares de chave-valor. Para acessar um valor, você precisa definir uma chave.
- Crie um
companion objectdentro da classeUserPreferencesRepository. - Use a função
booleanPreferencesKey()para definir uma chave e transmitir o nomeis_linear_layouta ela. Semelhante aos nomes de tabelas SQL, a chave precisa usar um formato de sublinhado. Essa chave é usada para acessar um valor booleano que indica se o layout linear precisa ser mostrado.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
}
...
}
Gravar no DataStore
Você cria e modifica os valores em um DataStore transmitindo uma lambda para o método edit(). A lambda recebe uma instância de MutablePreferences, que pode ser usada para atualizar valores no DataStore. Todas as atualizações dentro dessa lambda são executadas como uma única transação. Ou seja, a atualização é atômica: ela acontece de uma só vez. Esse tipo de atualização evita situações em que alguns valores são atualizados, mas outros não.
- Crie uma função de suspensão com o nome
saveLayoutPreference(). - Na função
saveLayoutPreference(), chame o métodoedit()no objetodataStore.
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit {
}
}
- Para que o código fique mais legível, defina um nome para as
MutablePreferencesfornecidas no corpo da lambda. Use essa propriedade para definir um valor com a chave definida e o booleano transmitido para a funçãosaveLayoutPreference().
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit { preferences ->
preferences[IS_LINEAR_LAYOUT] = isLinearLayout
}
}
Ler do DataStore
Agora que você criou uma maneira de gravar isLinearLayout no dataStore, siga estas etapas:
- Crie uma propriedade no
UserPreferencesRepositorydo tipoFlow<Boolean>e com o nomeisLinearLayout.
val isLinearLayout: Flow<Boolean> =
- É possível usar a propriedade
DataStore.datapara expor valoresDataStore. DefinaisLinearLayoutcomo a propriedadedatado objetoDataStore.
val isLinearLayout: Flow<Boolean> = dataStore.data
A propriedade data é um Flow de objetos Preferences. Os objetos Preferences contêm todos os pares de chave-valor no DataStore. Sempre que os dados no DataStore são atualizados, um novo objeto Preferences é emitido no Flow.
- Use a função "map" para converter o
Flow<Preferences>em umFlow<Boolean>.
Essa função aceita uma lambda com o objeto Preferences atual como parâmetro. Você pode especificar a chave definida anteriormente para acessar a preferência de layout. Não esqueça que o valor pode não existir se saveLayoutPreference ainda não tiver sido chamado. Portanto, também é necessário fornecer um valor padrão.
- Especifique
truepara adotar a visualização de layout linear por padrão.
val isLinearLayout: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
Como processar exceções
Sempre que você interage com o sistema de arquivos em um dispositivo, é possível que algo falhe. Por exemplo, um arquivo pode não existir ou o disco pode estar cheio ou desconectado. Como o DataStore lê e grava dados de arquivos, podem ocorrer IOExceptions ao acessar o DataStore. Use o operador catch{} para capturar exceções e lidar com essas falhas.
- No objeto complementar, implemente uma propriedade de string
TAGimutável que será usada para gerar registros.
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
const val TAG = "UserPreferencesRepo"
}
- O
Preferences DataStoregera umaIOExceptionquando um erro é encontrado durante a leitura de dados. No bloco de inicializaçãoisLinearLayout, antes demap(), use o operadorcatch{}para capturar aIOException.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
- No bloco "catch", se houver uma
IOexception, registre o erro e emitaemptyPreferences(). Se um tipo diferente de exceção for gerado, prefira gerá-la novamente. Ao emitiremptyPreferences()caso haja um erro, a função "map" ainda poderá mapear para o valor padrão.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {
if(it is IOException) {
Log.e(TAG, "Error reading preferences.", it)
emit(emptyPreferences())
} else {
throw it
}
}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
5. Inicializar o DataStore
Neste codelab, você vai precisar processar a injeção de dependências manualmente. Portanto, forneça um Preferences DataStore manualmente à classe UserPreferencesRepository. Siga estas etapas para injetar o DataStore no UserPreferencesRepository.
- Localize o pacote
dessertrelease. - Nesse diretório, crie uma nova classe com o nome
DessertReleaseApplicatione implemente a classeApplication. Este é o contêiner do seu DataStore.
class DessertReleaseApplication: Application() {
}
- No arquivo
DessertReleaseApplication.kt, mas fora da classeDessertReleaseApplication, declare um valorprivate const valcom o nomeLAYOUT_PREFERENCE_NAME. - Atribua à variável
LAYOUT_PREFERENCE_NAMEo valor de stringlayout_preferences, que pode ser usado como o nome doPreferences Datastoreque você vai instanciar na próxima etapa.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
- Ainda fora do corpo da classe
DessertReleaseApplication, mas no arquivoDessertReleaseApplication.kt, crie uma propriedade de valor particular do tipoDataStore<Preferences>com o nomeContext.dataStoreusando o delegadopreferencesDataStore. TransmitaLAYOUT_PREFERENCE_NAMEao parâmetronamedo delegadopreferencesDataStore.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
- No corpo da classe
DessertReleaseApplication, crie uma instâncialateinit vardoUserPreferencesRepository.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
}
- Modifique o método
onCreate().
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
override fun onCreate() {
super.onCreate()
}
}
- No método
onCreate(), inicializeuserPreferencesRepositorycriando umUserPreferencesRepositorycom odataStorecomo parâmetro.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
class DessertReleaseApplication: Application() {
lateinit var userPreferencesRepository: UserPreferencesRepository
override fun onCreate() {
super.onCreate()
userPreferencesRepository = UserPreferencesRepository(dataStore)
}
}
- Adicione a linha mostrada abaixo à tag
<application>no arquivoAndroidManifest.xml.
<application
android:name=".DessertReleaseApplication"
...
</application>
Essa abordagem define a classe DessertReleaseApplication como o ponto de entrada do app. O objetivo desse código é inicializar as dependências definidas na classe DessertReleaseApplication antes de iniciar a MainActivity.
6. Usar o UserPreferencesRepository
Fornecer o repositório ao ViewModel
Agora que o UserPreferencesRepository está disponível por injeção de dependência, você pode usá-lo no DessertReleaseViewModel.
- No
DessertReleaseViewModel, crie uma propriedadeUserPreferencesRepositorycomo um parâmetro construtor.
class DessertReleaseViewModel(
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
...
}
- No objeto complementar do
ViewModel, no blocoviewModelFactory initializer, receba uma instância doDessertReleaseApplicationusando o código abaixo.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
...
}
}
}
}
- Crie uma instância do
DessertReleaseViewModele transmita ouserPreferencesRepository.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
DessertReleaseViewModel(application.userPreferencesRepository)
}
}
}
}
O UserPreferencesRepository agora pode ser acessado pelo ViewModel. As próximas etapas envolvem usar os recursos de leitura e gravação do UserPreferencesRepository que você implementou anteriormente.
Armazenar a preferência de layout
- Edite a função
selectLayout()noDessertReleaseViewModelpara acessar o repositório de preferências e atualizar a preferência de layout. - A gravação no
DataStoreé feita de forma assíncrona com uma funçãosuspend. Inicie uma nova corrotina para chamar a funçãosaveLayoutPreference()do repositório de preferências.
fun selectLayout(isLinearLayout: Boolean) {
viewModelScope.launch {
userPreferencesRepository.saveLayoutPreference(isLinearLayout)
}
}
Ler a preferência de layout
Nesta seção, você vai refatorar o uiState: StateFlow existente no ViewModel para refletir o isLinearLayout: Flow do repositório.
- Exclua o código que inicializa a propriedade
uiStatecomoMutableStateFlow(DessertReleaseUiState).
val uiState: StateFlow<DessertReleaseUiState> =
A preferência de layout linear do repositório tem dois valores possíveis, verdadeiro ou falso, na forma de um Flow<Boolean>. Esse valor precisa ser mapeado para um estado da interface.
- Defina o
StateFlowcomo o resultado da transformação de coleçãomap(), chamada emisLinearLayout Flow.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
}
- Retorne uma instância da classe de dados
DessertReleaseUiState, transmitindoisLinearLayout Boolean. A tela usa esse estado da interface para determinar as strings e os ícones corretos a serem mostrados.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
O UserPreferencesRepository.isLinearLayout é um Flow que é frio. No entanto, para fornecer estados à interface, é melhor usar um fluxo quente, como StateFlow, para que o estado esteja sempre disponível imediatamente.
- Use a função
stateIn()para converter umFlowem umStateFlow. - A função
stateIn()aceita três parâmetros:scope,startedeinitialValue. TransmitaviewModelScope,SharingStarted.WhileSubscribed(5_000)eDessertReleaseUiState()para esses parâmetros, respectivamente.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = DessertReleaseUiState()
)
- Inicie o app. É possível clicar no ícone para alternar entre um layout de grade e um layout linear.

Parabéns! Você adicionou o Preferences DataStore ao app para salvar a preferência de layout do usuário.
7. Acessar o código da solução
Para baixar o código do codelab concluído, use estes comandos git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-release.git $ cd basic-android-kotlin-compose-training-dessert-release $ git checkout main
Se preferir, você pode baixar o repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Se você quiser conferir o código da solução, acesse o GitHub (em inglês).