1. Avant de commencer
Introduction
Dans ce module, vous avez appris à enregistrer des données localement sur un appareil à l'aide de SQL et de Room. SQL et Room sont des outils puissants. Toutefois, si vous n'avez pas besoin de stocker de données relationnelles, DataStore peut fournir une solution simple. Le composant Jetpack DataStore est un excellent moyen de stocker de petits ensembles de données simples en ne consommant que peu de ressources. DataStore propose deux implémentations différentes : Preferences DataStore et Proto DataStore.
Preferences DataStorestocke les paires clé-valeur. Les valeurs peuvent appartenir aux types de données de base de Kotlin, tels queString,BooleanetInteger. Cette implémentation ne stocke pas d'ensembles de données complexes. Elle ne nécessite pas de schéma prédéfini. Le principal cas d'utilisation dePreferences Datastoreconsiste à stocker les préférences sur l'appareil de l'utilisateur.Proto DataStorestocke des types de données personnalisés. Cette implémentation nécessite un schéma prédéfini, qui mappe des définitions de protocole avec des structures d'objets.
Cet atelier de programmation ne traite que de Preferences DataStore. Pour en savoir plus sur Proto DataStore, consultez la documentation dédiée à DataStore.
Preferences DataStore est un excellent moyen de stocker des paramètres contrôlés par l'utilisateur. Dans cet atelier de programmation, vous allez apprendre à implémenter DataStore à cette fin.
Conditions préalables :
- Vous avez suivi le cours sur les principes de base d'Android avec Compose dans le cadre de l'atelier de programmation Lire et mettre à jour des données avec Room.
Ce dont vous avez besoin
- Un ordinateur avec un accès à Internet et Android Studio
- Un appareil ou un émulateur
- Le code de démarrage pour l'application Dessert Release
Objectifs de l'atelier
L'application Dessert Release affiche la liste des versions Android. L'icône de la barre d'application permet de passer du mode Grille au mode Liste.

Dans son état actuel, l'application ne conserve pas la mise en page sélectionnée. Lorsque vous fermez l'application, votre sélection n'est pas enregistrée et la mise en page par défaut est rétablie. Dans cet atelier de programmation, vous allez ajouter DataStore à l'application Dessert Release et l'utiliser pour stocker la préférence de mise en page sélectionnée.
2. Télécharger le code de démarrage
Cliquez sur le lien ci-dessous pour télécharger l'ensemble du code de cet atelier de programmation :
Ou, si vous préférez, vous pouvez cloner le code de la version Dessert à partir de 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
- Dans Android Studio, ouvrez le dossier
basic-android-kotlin-compose-training-dessert-release. - Ouvrez le code de l'application Dessert Release dans Android Studio.
3. Configurer des dépendances
Ajoutez ce qui suit à dependencies dans le fichier app/build.gradle.kts :
implementation("androidx.datastore:datastore-preferences:1.0.0")
4. Implémenter le dépôt de préférences utilisateur
- Dans le package
data, créez une classe intituléeUserPreferencesRepository.

- Dans le constructeur
UserPreferencesRepository, définissez une propriété de valeur privée pour représenter une instance d'objetDataStorede typePreferences.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
}
DataStore stocke les paires clé-valeur. Pour accéder à une valeur, vous devez définir une clé.
- Créez un
companion objectdans la classeUserPreferencesRepository. - Utilisez la fonction
booleanPreferencesKey()pour définir une clé et lui transmettre le nomis_linear_layout. Comme pour les noms de tables SQL, les espaces dans le nom de la clé doivent être remplacés par des traits de soulignement. Cette clé permet d'accéder à une valeur booléenne indiquant si la mise en page linéaire doit être affichée.
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>
){
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
}
...
}
Écrire dans le DataStore
Vous créez et modifiez les valeurs dans un DataStore en transmettant un lambda à la méthode edit(). Le lambda reçoit une instance de MutablePreferences, que vous pouvez utiliser pour mettre à jour les valeurs dans DataStore. Toutes les mises à jour de ce lambda sont exécutées comme une transaction unique. En d'autres termes, la mise à jour est atomique. Tout est géré simultanément. Ce type de mise à jour permet d'éviter que certaines valeurs se mettent à jour, mais pas d'autres.
- Créez une fonction de suspension et appelez-la
saveLayoutPreference(). - Dans la fonction
saveLayoutPreference(), appelez la méthodeedit()sur l'objetdataStore.
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit {
}
}
- Pour rendre votre code plus lisible, définissez un nom pour l'élément
MutablePreferencesfourni dans le corps du lambda. Utilisez cette propriété pour déterminer une valeur avec la clé que vous avez définie et la valeur booléenne transmise à la fonctionsaveLayoutPreference().
suspend fun saveLayoutPreference(isLinearLayout: Boolean) {
dataStore.edit { preferences ->
preferences[IS_LINEAR_LAYOUT] = isLinearLayout
}
}
Lire depuis le DataStore
Maintenant que vous avez créé un moyen d'écrire isLinearLayout dans dataStore, procédez comme suit pour le lire :
- Dans
UserPreferencesRepository, créez une propriété de typeFlow<Boolean>appeléeisLinearLayout.
val isLinearLayout: Flow<Boolean> =
- Vous pouvez utiliser la propriété
DataStore.datapour exposer les valeursDataStore. DéfinissezisLinearLayoutsur la propriétédatade l'objetDataStore.
val isLinearLayout: Flow<Boolean> = dataStore.data
La propriété data est un Flow d'objets Preferences. L'objet Preferences contient toutes les paires clé-valeur du DataStore. Chaque fois que les données dans le DataStore sont mises à jour, un nouvel objet Preferences est émis dans Flow.
- Utilisez la fonction Map pour convertir
Flow<Preferences>enFlow<Boolean>.
Cette fonction accepte un lambda avec l'objet Preferences actuel comme paramètre. Vous pouvez spécifier la clé que vous avez définie précédemment pour obtenir la préférence de mise en page. Gardez à l'esprit qu'il se peut que la valeur n'existe pas si saveLayoutPreference n'a pas encore été appelé. Vous devez donc également fournir une valeur par défaut.
- Spécifiez
truepour utiliser par défaut la mise en page en lignes.
val isLinearLayout: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
Gestion des exceptions
Chaque interaction avec le système de fichiers d'un appareil peut donner lieu à un dysfonctionnement. Par exemple, il se peut qu'un fichier n'existe pas, que le disque soit saturé ou qu'il ait été retiré. DataStore lit et écrit des données à partir de fichiers. Des IOExceptions peuvent survenir lorsque vous accédez à DataStore. Utilisez l'opérateur catch{} pour intercepter les exceptions et gérer ces échecs.
- Dans l'objet associé, implémentez une propriété de chaîne
TAGimmuable pour la journalisation.
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
const val TAG = "UserPreferencesRepo"
}
Preferences DataStoregénère uneIOExceptionsi une erreur se produit lors de la lecture des données. Dans le bloc d'initialisationisLinearLayout, avantmap(), utilisez l'opérateurcatch{}pour intercepter l'IOException.
val isLinearLayout: Flow<Boolean> = dataStore.data
.catch {}
.map { preferences ->
preferences[IS_LINEAR_LAYOUT] ?: true
}
- Dans le bloc d'interception, si une
IOexceptionsurvient, consignez l'erreur et émettezemptyPreferences(). Si une exception d'un autre type survient, il est préférable de la renvoyer. En émettantemptyPreferences()en cas d'erreur, la fonction Map peut toujours être mappée sur la valeur par défaut.
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. Initialiser le DataStore
Dans cet atelier de programmation, vous devez gérer manuellement l'injection de dépendances. Par conséquent, vous devez fournir manuellement une classe UserPreferencesRepository avec Preferences DataStore. Procédez comme suit pour injecter DataStore dans UserPreferencesRepository.
- Recherchez le package
dessertrelease. - Dans ce répertoire, créez une classe appelée
DessertReleaseApplicationet implémentez la classeApplication. Il s'agit du conteneur de votre DataStore.
class DessertReleaseApplication: Application() {
}
- Dans le fichier
DessertReleaseApplication.kt, mais en dehors de la classeDessertReleaseApplication, déclarez unprivate const valappeléLAYOUT_PREFERENCE_NAME. - Attribuez à la variable
LAYOUT_PREFERENCE_NAMEla valeur de chaînelayout_preferences, que vous pourrez ensuite utiliser comme nom duPreferences Datastorequi sera instancié à l'étape suivante.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
- Toujours en dehors du corps de la classe
DessertReleaseApplication, mais dans le fichierDessertReleaseApplication.kt, créez une propriété de valeur privée de typeDataStore<Preferences>, appeléeContext.dataStore, à l'aide du déléguépreferencesDataStore. TransmettezLAYOUT_PREFERENCE_NAMEpour le paramètrenamedu déléguépreferencesDataStore.
private const val LAYOUT_PREFERENCE_NAME = "layout_preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCE_NAME
)
- Dans le corps de la classe
DessertReleaseApplication, créez une instancelateinit varduUserPreferencesRepository.
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
}
- Remplacez la méthode
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()
}
}
- Dans la méthode
onCreate(), initialisezuserPreferencesRepositoryen construisant unUserPreferencesRepositoryavecdataStorecomme paramètre.
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)
}
}
- Ajoutez la ligne suivante dans la balise
<application>du fichierAndroidManifest.xml.
<application
android:name=".DessertReleaseApplication"
...
</application>
Cette approche définit la classe DessertReleaseApplication comme point d'entrée de l'application. Ce code vise à initialiser les dépendances définies dans la classe DessertReleaseApplication avant de lancer MainActivity.
6. Utiliser UserPreferencesRepository
Fournir le dépôt au ViewModel
Maintenant que UserPreferencesRepository est disponible par injection de dépendances, vous pouvez l'utiliser dans DessertReleaseViewModel.
- Dans
DessertReleaseViewModel, créez une propriétéUserPreferencesRepositoryen tant que paramètre constructeur.
class DessertReleaseViewModel(
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
...
}
- Dans l'objet associé à
ViewModel, dans le blocviewModelFactory initializer, obtenez une instance deDessertReleaseApplicationà l'aide du code suivant.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
...
}
}
}
}
- Créez une instance de
DessertReleaseViewModelet transmettezuserPreferencesRepository.
...
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as DessertReleaseApplication)
DessertReleaseViewModel(application.userPreferencesRepository)
}
}
}
}
La classe UserPreferencesRepository est désormais accessible par le ViewModel. Les étapes suivantes consistent à utiliser les fonctionnalités de lecture et d'écriture de UserPreferencesRepository, que vous avez implémentées précédemment.
Enregistrer la préférence de mise en page
- Modifiez la fonction
selectLayout()dansDessertReleaseViewModelpour accéder au dépôt de préférences et mettre à jour la préférence de mise en page. - N'oubliez pas que l'écriture dans
DataStoreest effectuée de manière asynchrone avec une fonctionsuspend. Démarrez une nouvelle coroutine pour appeler la fonctionsaveLayoutPreference()du dépôt de préférences.
fun selectLayout(isLinearLayout: Boolean) {
viewModelScope.launch {
userPreferencesRepository.saveLayoutPreference(isLinearLayout)
}
}
Lire la préférence de mise en page
Dans cette section, vous allez refactoriser le uiState: StateFlow existant dans le ViewModel pour refléter le isLinearLayout: Flow du dépôt.
- Supprimez le code qui initialise la propriété
uiStatesurMutableStateFlow(DessertReleaseUiState).
val uiState: StateFlow<DessertReleaseUiState> =
Dans le dépôt, la préférence pour une mise en page en lignes peut être "true" ou "false", et est indiquée sous forme de Flow<Boolean>. Cette valeur doit être mappée à un état de l'interface utilisateur.
- Définissez
StateFlowsur le résultat de la transformation de collectionmap()appelée surisLinearLayout Flow.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
}
- Renvoyez une instance de la classe de données
DessertReleaseUiStateen transmettantisLinearLayout Boolean. L'écran utilise cet état d'interface utilisateur pour déterminer les chaînes et les icônes à afficher.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
UserPreferencesRepository.isLinearLayout est un Flow froid. Toutefois, pour fournir l'état à l'interface utilisateur, il est préférable d'utiliser un flux chaud tel que StateFlow, afin que l'état soit toujours disponible immédiatement.
- Utilisez la fonction
stateIn()pour convertir unFlowenStateFlow. - La fonction
stateIn()accepte trois paramètres :scope,startedetinitialValue. Transmettez respectivementviewModelScope,SharingStarted.WhileSubscribed(5_000)etDessertReleaseUiState()pour ces paramètres.
val uiState: StateFlow<DessertReleaseUiState> =
userPreferencesRepository.isLinearLayout.map { isLinearLayout ->
DessertReleaseUiState(isLinearLayout)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = DessertReleaseUiState()
)
- Lancez l'application. Notez que vous pouvez cliquer sur l'icône de mise en page pour passer du mode Grille au mode Liste.

Félicitations ! Vous avez bien ajouté Preferences DataStore à votre application pour enregistrer les préférences de mise en page de l'utilisateur.
7. Télécharger le code de solution
Pour télécharger le code de cet atelier de programmation terminé, utilisez les commandes Git suivantes :
$ 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
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.
Si vous souhaitez voir le code de solution, affichez-le sur GitHub.