DataStore est un composant de la bibliothèque Jetpack conçu pour stocker de petites quantités de données dans les applications Android. Contrairement à SharedPreferences, il fonctionne de manière asynchrone et garantit la cohérence des données en cas d'accès concurrent. Selon Google, 2024, DataStore utilise Kotlin Coroutines et Flow, ce qui le rend sûr pour le thread principal et adapté aux architectures réactives.
Points Clés
DataStore est une solution de Google pour le stockage local de données dans Android, présentée en 2020 comme alternative à SharedPreferences. Il prend en charge deux modes : Preferences DataStore (paires simples clé-valeur) et Proto DataStore (schéma typé basé sur Protocol Buffers).
Le principal avantage est l'asynchronie totale : toutes les opérations de lecture retournent un Flow de Kotlin Coroutines, et les écritures sont effectuées dans un contexte de coroutine. Cela élimine le blocage du thread principal, qui était un problème typique de SharedPreferences lors du traitement de gros volumes de données.
DataStore garantit l'atomicité des opérations : les écritures concurrentes n'entraînent pas de perte de données grâce à son modèle transactionnel. Si deux composants modifient la même valeur simultanément, DataStore gère correctement le conflit via un mécanisme compare-and-swap.
Selon Google I/O 2023, DataStore est utilisé dans 40 % des nouveaux projets Android, et Google recommande de migrer depuis SharedPreferences dans toutes les applications où la stabilité du stockage des paramètres est requise.
Au cœur de DataStore se trouve SingleProcessDataStore — une implémentation qui opère au sein d'un seul processus. Il utilise un stockage basé sur des fichiers avec verrouillage au niveau du fichier : lors de l'écriture de données, le fichier est verrouillé, empêchant la corruption en cas d'accès concurrent.
DataStore gère automatiquement les erreurs de désérialisation : si le fichier est corrompu, il retourne une valeur par défaut et réécrit le fichier. Ce comportement est configurable via corruptionHandler, qui peut être défini lors de la création du DataStore.
SharedPreferences souffre de trois problèmes fondamentaux : lecture synchrone du disque sur le thread principal, absence de garanties d'atomicité pour les écritures concurrentes et impossibilité de suivre les modifications de manière réactive. DataStore résout les trois : Flow pour l'observation, verrouillage de fichier pour l'atomicité et API asynchrone pour la sécurité des threads.
DataStore stocke les données dans des fichiers sur le stockage interne de l'appareil. Preferences DataStore utilise un format de fichier similaire à SharedPreferences mais avec des métadonnées supplémentaires pour la vérification d'intégrité. Proto DataStore utilise le format binaire Protocol Buffers, ce qui réduit la taille du fichier et accélère la sérialisation.
Lors de la lecture des données, DataStore charge l'intégralité du fichier en mémoire une fois, après quoi les abonnés reçoivent l'état actuel via Flow. Les modifications sont diffusées automatiquement à tous les abonnés actifs — aucun enregistrement manuel d'écouteurs n'est requis, contrairement à SharedPreferences.
Preferences DataStore utilise un mécanisme de sérialisation intégré basé sur une Map. Chaque entrée est une paire d'une chaîne et d'un type primitif (Int, Boolean, Float, Long, String, Set). Les données sont stockées dans un fichier XML, similaire à SharedPreferences, mais avec une écriture atomique via le verrouillage de fichier.
Exemple de création de Preferences DataStore : l'extension preferencesDataStore sur Context crée un singleton avec le nom du fichier. Lors d'appels répétés, la même instance est retournée — cela élimine la duplication de fichiers et la confusion entre différentes instances de stockage.
Proto DataStore nécessite la définition d'un schéma de données via un fichier .proto et la compilation avec le plugin protobuf. La classe Java générée est utilisée comme point d'entrée unique pour tous les champs — cela élimine les fautes de frappe dans les clés, courantes avec SharedPreferences.
Le schéma Proto DataStore est défini une fois et prend en charge l'ajout de nouveaux champs sans perdre les anciennes données. Si une nouvelle version de l'application ajoute un champ avec une valeur par défaut, l'ancien fichier sera correctement désérialisé — la compatibilité ascendante est intégrée dans le protocole.
Le choix entre Preferences DataStore et Proto DataStore dépend de la complexité des données et des exigences de typage. Les deux options sont asynchrones et transactionnelles, mais diffèrent en matière de sécurité de type et de performances de sérialisation.
| Caractéristique | Preferences DataStore | Proto DataStore |
|---|---|---|
| Typage | Faible (clé-valeur) | Fort (classe générée) |
| Sérialisation | XML (intégrée) | Protocol Buffers (protobuf) |
| Taille du fichier | Grande (XML lisible) | Petite (binaire) |
| Complexité | Faible (sans .proto) | Moyenne (nécessite .proto) |
| Migration de schéma | Pas de schéma | Automatique (proto) |
| Compatibilité | SharedPreferences (via migration) | Proto DataStore uniquement |
Preferences DataStore convient pour les paramètres simples : indicateurs de fonctionnalités, chaîne de jeton d'autorisation, nombre de lancements de l'application. Si les données sont peu nombreuses (jusqu'à 10–15 clés) et ne nécessitent pas de schéma strict, Preferences DataStore offre un seuil d'entrée minimal sans connexion du plugin protobuf.
Proto DataStore est justifié lorsque la structure des données est complexe ou peut changer entre les versions de l'application. Par exemple, les paramètres de profil utilisateur ou la configuration de tests A/B avec 20+ champs. Protobuf fournit un typage fort et des migrations automatiques, éliminant les erreurs d'exécution dues à des discordances de clés.
Google fournit un mécanisme de migration intégré via la classe SharedPreferencesMigration. La migration est effectuée une fois au premier démarrage après la mise à jour de l'application : DataStore lit les données de SharedPreferences, les écrit dans son propre format et marque la migration comme terminée.
La migration prend en charge les transformations personnalisées : si les clés dans SharedPreferences ne correspondent pas aux clés souhaitées de DataStore, vous pouvez spécifier une fonction de transformation via SharedPreferencesMigration. Cela permet de renommer les clés et de modifier les types de données pendant la migration.
Premièrement, ajoutez DataStore à build.gradle et créez une instance DataStore avec migration : SharedPreferencesMigration accepte le nom du fichier SharedPreferences et un ensemble de clés à transférer. Deuxièmement, supprimez tout le code fonctionnant via SharedPreferences et remplacez-le par des appels DataStore. Troisièmement, testez la migration : au premier démarrage, les données doivent apparaître dans DataStore et l'ancien fichier SharedPreferences ne doit plus être utilisé.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore s'intègre facilement dans un projet existant. Voici des exemples pratiques pour Preferences DataStore et Proto DataStore — tous deux démontrent la lecture, l'écriture et l'observation réactive des données.
Dans cet exemple, Preferences DataStore stocke trois paramètres : le thème sombre, le nom d'utilisateur et le compteur de lancements. La lecture se fait via l'extension .data, qui retourne un Flow. L'écriture se fait via la fonction suspend .edit, qui garantit l'atomicité des modifications.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore nécessite la définition d'un fichier .proto. Après compilation, une classe UserSettings est créée et utilisée pour la lecture et l'écriture. Les migrations de version du schéma sont décrites dans le même fichier .proto et appliquées automatiquement.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Lecture depuis DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Écriture de nouvelles valeurs
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore s'intègre avec l'architecture MVVM via ViewModel. Le Flow de DataStore est collecté via .stateIn et utilisé dans l'interface utilisateur. À chaque modification de données, l'interface utilisateur se met à jour automatiquement — aucune mise à jour manuelle ou LiveData n'est nécessaire.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
Questions Fréquentes
DataStore fonctionne de manière asynchrone (ne bloque pas le thread UI), prend en charge l'accès concurrent via des transactions et permet l'abonnement réactif aux changements via Flow. SharedPreferences est une API synchrone avec un risque d'ANR avec de gros volumes de données et sans support réactif intégré.
DataStore est écrit en Kotlin et nécessite Kotlin Coroutines. L'utiliser depuis Java est possible mais peu pratique : il faudrait créer des wrappers avec CompletableFuture ou gérer manuellement les coroutines. Pour les projets Java, Google recommande de conserver SharedPreferences ou d'ajouter Kotlin au module.
DataStore charge l'intégralité du fichier en mémoire lors de la lecture, il n'est donc pas adapté au stockage de listes ou d'objets volumineux. Pour de tels scénarios, utilisez Room ou SQLite. DataStore est optimisé pour les paramètres et les petites données structurées — jusqu'à des centaines de kilo-octets.
Lors de la création d'un DataStore, vous pouvez passer un corruptionHandler — une fonction appelée lorsque le fichier est corrompu. Par défaut, DataStore lève une exception CorruptionException. Dans le corruptionHandler, vous pouvez retourner des données vides, après quoi DataStore réécrira le fichier dans un état correct.
Oui, Proto DataStore nécessite la définition d'un schéma dans un fichier .proto et la connexion du protobuf-gradle-plugin. Si le projet est petit et les données simples, il est plus facile d'utiliser Preferences DataStore — il ne nécessite pas de configuration de build supplémentaire.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi