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 DataStore
stocke les paires clé-valeur. Les valeurs peuvent appartenir aux types de données de base de Kotlin, tels queString
,Boolean
etInteger
. 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 Datastore
consiste à stocker les préférences sur l'appareil de l'utilisateur.Proto DataStore
stocke 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'objetDataStore
de 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 object
dans 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
MutablePreferences
fourni 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.data
pour exposer les valeursDataStore
. DéfinissezisLinearLayout
sur la propriétédata
de 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
true
pour 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
TAG
immuable pour la journalisation.
private companion object {
val IS_LINEAR_LAYOUT = booleanPreferencesKey("is_linear_layout")
const val TAG = "UserPreferencesRepo"
}
Preferences DataStore
génère uneIOException
si 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
IOexception
survient, 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
DessertReleaseApplication
et 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 val
appeléLAYOUT_PREFERENCE_NAME
. - Attribuez à la variable
LAYOUT_PREFERENCE_NAME
la valeur de chaînelayout_preferences
, que vous pourrez ensuite utiliser comme nom duPreferences Datastore
qui 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_NAME
pour le paramètrename
du 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 var
duUserPreferencesRepository
.
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()
, initialisezuserPreferencesRepository
en construisant unUserPreferencesRepository
avecdataStore
comme 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éUserPreferencesRepository
en 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
DessertReleaseViewModel
et 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()
dansDessertReleaseViewModel
pour 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
DataStore
est 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é
uiState
surMutableStateFlow(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
StateFlow
sur 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
DessertReleaseUiState
en 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 unFlow
enStateFlow
. - La fonction
stateIn()
accepte trois paramètres :scope
,started
etinitialValue
. 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.