Stockage des données sur Android
Kotlin
Android
Stockage
Lecture
Découvrez les différentes solutions de stockage des données sur Android.
Pourquoi stocker des données dans une application mobile ?
- Persistance entre sessions : Conserver les informations même après fermeture de l’application
- Expérience utilisateur fluide : Éviter de redemander les mêmes informations à chaque démarrage
- Fonctionnement hors-ligne : Permettre l’utilisation de l’application sans connexion internet
- Personnalisation : Adapter l’application aux préférences et comportements de l’utilisateur
- Performances : Mettre en cache des données pour améliorer la réactivité de l’application
- Réduction du trafic réseau : Limiter les requêtes vers des services distants
Stockage à court terme vs stockage persistant
| Stockage à court terme | Stockage persistant |
|---|---|
| - Variables en mémoire | - SharedPreferences |
| - Bundle d’instance d’activité | - Fichiers internes/externes |
| - Cache applicatif | - Base de données (SQLite/Room) |
| - Stockage dans le cloud |
Stockage local vs stockage cloud
| Stockage local | Stockage cloud |
|---|---|
| Avantages : • Disponibilité immédiate • Fonctionne hors-ligne • Pas de coûts supplémentaires • Accès rapide aux données • Contrôle total sur les données |
Avantages : • Synchronisation multi-appareils • Sauvegarde automatique • Collaboration en temps réel • Espace de stockage évolutif • Persistance au-delà du cycle de vie de l’appareil |
| Inconvénients : • Limité à l’appareil • Risque de perte en cas de désinstallation/réinitialisation • Capacité de stockage limitée • Pas de partage natif entre appareils • Vulnérable aux problèmes matériels |
Inconvénients : • Nécessite une connexion internet • Coûts potentiels (abonnements, consommation data) • Complexité de mise en œuvre • Latence possible • Risques de confidentialité et sécurité |
Comparaison
| Solution de stockage | Performances | Complexité | Volume de données | Cas d’usage typiques |
|---|---|---|---|---|
| SharedPreferences | Élevées | Simple | Faible | Paramètres utilisateur, états d’UI |
| Fichiers internes | Bonnes | Modérée | Moyen | Documents, fichiers temporaires, cache structuré |
| Fichiers externes | Bonnes | Modérée+ | Élevé | Médias, exports, partage de fichiers |
| SQLite/Room | Moyennes | Élevée | Élevé | Données structurées complexes, relations |
| Firebase | Variables* | Élevée | Variable | Synchronisation multi-appareils, temps réel |
* Dépend de la qualité de la connexion internet
Sélectionner la solution adaptée
Questions clés à se poser :
- Quelle est la nature des données à stocker ?
- Quelle quantité de données faut-il gérer ?
- Faut-il partager ces données avec d’autres applications ?
- Les données doivent-elles être synchronisées entre appareils ?
- Quelle importance ont la sécurité et la confidentialité ?
Exemple d’utilisation de Preferences DataStore
// Exemple d'utilisation de Preferences DataStore
val FITNESS_PREFERENCES_NAME = "fitness_preferences"
// Création du DataStore
val Context.dataStore by preferencesDataStore(name = FITNESS_PREFERENCES_NAME)
// Définition des clés
val USERNAME = stringPreferencesKey("username")
val DAILY_GOAL = intPreferencesKey("daily_steps_goal")
val NOTIFICATIONS_ENABLED = booleanPreferencesKey("notifications_enabled")
// Lecture de données avec Flow
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
UserPreferences(
username = preferences[USERNAME] ?: "",
dailyStepsGoal = preferences[DAILY_GOAL] ?: 10000,
notificationsEnabled = preferences[NOTIFICATIONS_ENABLED] ?: true
)
}
// Écriture de données
suspend fun updateDailyGoal(newGoal: Int) {
dataStore.edit { preferences ->
preferences[DAILY_GOAL] = newGoal
}
}Stockage interne de fichiers (1/2)
Définition
- Espace de stockage privé à l’application
- Accès limité à l’application propriétaire
- Localisation:
/data/data/[package_name]/files/ - Supprimé automatiquement lors de la désinstallation |
Avantages
- Pas de permissions requises
- Sécurité garantie (sandbox)
- Accès rapide
- Idéal pour données sensibles
Stockage interne de fichiers (2/2)
Limitations
Non visible via gestionnaire de fichiers
Non partageable facilement
Limité à la durée de vie de l’app
Cas d’usage
- Fichiers texte (journaux, logs)
- Données structurées (JSON, XML)
- Cache persistant
- Configurations applicatives
Exemple d’utilisation
// Écriture dans un fichier
fun saveNote(content: String) {
try {
// Mode privé: accessible uniquement par cette app
openFileOutput("note.txt", Context.MODE_PRIVATE).use { stream ->
stream.write(content.toByteArray())
}
Toast.makeText(this, "Note sauvegardée", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(this, "Erreur: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
// Lecture d'un fichier
fun loadNote(): String {
return try {
openFileInput("note.txt").use { stream ->
String(stream.readBytes())
}
} catch (e: FileNotFoundException) {
"Aucune note sauvegardée"
} catch (e: Exception) {
"Erreur: ${e.message}"
}
}
// Utilisation dans une activité
binding.btnSave.setOnClickListener {
val noteContent = binding.editNote.text.toString()
saveNote(noteContent)
}
binding.btnLoad.setOnClickListener {
binding.textNote.text = loadNote()
}Stockage externe
Stockage public
Accessible à toutes les applications
Visible à l’utilisateur dans l’explorateur de fichiers
Requiert des permissions spécifiques
Conservation après désinstallation de l’app
Stockage spécifique à l’app
- Réservé à l’application (sauf root)
- Supprimé à la désinstallation
- Ne nécessite pas de permissions spéciales
- Stockage dans
/Android/data/[package_name]/
Media Store
- API dédiée aux médias (images, vidéos, audio)
- Accès standardisé via ContentProvider
- Recherche et filtrage avancés
- Indexation automatique
Permissions et évolution
| Version Android | Approche d’accès au stockage |
|---|---|
| < Android 10 (Q) | - Permissions larges: READ_EXTERNAL_STORAGE et WRITE_EXTERNAL_STORAGE- Accès complet au stockage externe partagé |
| Android 10 (Q) | - Introduction du Scoped Storage - Possibilité d’opt-out temporaire via requestLegacyExternalStorage |
| Android 11+ | - Scoped Storage obligatoire - Accès limité aux répertoires spécifiques via MediaStore - Permission MANAGE_EXTERNAL_STORAGE pour cas exceptionnels |
Accès moderne au stockage externe
// Stockage spécifique à l'application (ne nécessite pas de permissions)
fun saveTrackingDataToAppStorage(context: Context, filename: String, data: String) {
// Obtention du répertoire "Documents" spécifique à l'application
val appDocumentsDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
val file = File(appDocumentsDir, filename)
try {
FileOutputStream(file).use { output ->
output.write(data.toByteArray())
}
Log.d("StorageExample", "Fichier sauvegardé: ${file.absolutePath}")
} catch (e: Exception) {
Log.e("StorageExample", "Erreur de sauvegarde", e)
}
}
// Accès au MediaStore pour sauvegarder une image dans Photos
@RequiresApi(Build.VERSION_CODES.Q)
fun saveImageToMediaStore(context: Context, bitmap: Bitmap, filename: String) {
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, filename)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/FitnessApp")
put(MediaStore.Images.Media.IS_PENDING, 1)
}
val resolver = context.contentResolver
val imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
imageUri?.let { uri ->
resolver.openOutputStream(uri)?.use { output ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, output)
}
contentValues.clear()
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
resolver.update(uri, contentValues, null, null)
Toast.makeText(context, "Image sauvegardée dans la galerie", Toast.LENGTH_SHORT).show()
}
}Room Database
- Définition
- Bibliothèque de persistence fournie par Android Jetpack
- Abstraction de SQLite facilitant l’accès aux bases de données
- Solution recommandée par Google pour le stockage structuré
- Avantages principaux
- Vérification des requêtes SQL à la compilation
- Réduction du code boilerplate et des erreurs d’exécution
- Intégration native avec LiveData, Flow et Coroutines
- Mapping objet-relationnel (ORM) moderne et optimisé
- Migrations de schéma simplifiées
- Architecture des composants
- Annotations pour générer le code d’accès à la base de données
- Séparation claire des responsabilités (entités, DAO, DB)
- Support des relations entre tables (One-to-One, One-to-Many, Many-to-Many)
Composants principaux de Room
| Composant | Description | Annotation |
|---|---|---|
| Entités | Classes Kotlin représentant les tables Définition des colonnes, index et contraintes |
@Entity |
| DAOs | Interfaces définissant les méthodes d’accès Opérations CRUD et requêtes SQL personnalisées |
@Dao |
| Database | Classe abstraite qui connecte les DAOs Gestion de la création et migration de la base |
@Database |
Migrations de schéma
- Gestion du versionnement de la base de données
- Mise à jour des schémas sans perte de données utilisateur
- Stratégies de test pour valider les migrations
Exemple d’utilisation de Room
// Définir une entité
@Entity(tableName = "workouts")
data class Workout(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val exerciseName: String,
val sets: Int,
val repetitions: Int,
val weight: Float,
val date: Long,
val duration: Int,
val caloriesBurned: Int
)
// Définir un DAO
@Dao
interface WorkoutDao {
@Insert
fun insertWorkout(workout: Workout): Long
@Update
fun updateWorkout(workout: Workout)
@Delete
fun deleteWorkout(workout: Workout)
@Query("SELECT * FROM workouts ORDER BY date DESC")
fun getAllWorkouts(): LiveData<List<Workout>>
}
// Définir la base de données
@Database(entities = [Workout::class], version = 1)
abstract class WorkoutDatabase : RoomDatabase() {
abstract fun workoutDao(): WorkoutDao
}Stockage dans le cloud avec Firebase
- Présentation de Firebase
- Services offerts par Firebase
- Avantages d’un backend as a service (BaaS)
- Firebase Realtime Database
- Structure de données en temps réel
- Synchronisation automatique
- Firebase Cloud Firestore
- Base de données NoSQL orientée documents
- Modèle de requêtes avancées
- Firebase Authentication
- Gestion des utilisateurs
- Sécurisation des données
Exemple d’utilisation de Firebase Cloud Firestore
// Configurer Firebase
// (Configuration préalable dans le fichier build.gradle et google-services.json)
// Sauvegarder un workout dans Firestore
fun saveWorkoutToCloud(workout: Workout) {
val db = FirebaseFirestore.getInstance()
val userId = FirebaseAuth.getInstance().currentUser?.uid
if (userId != null) {
val workoutMap = hashMapOf(
"exerciseName" to workout.exerciseName,
"sets" to workout.sets,
"repetitions" to workout.repetitions,
"weight" to workout.weight,
"date" to workout.date,
"duration" to workout.duration,
"caloriesBurned" to workout.caloriesBurned
)
db.collection("users").document(userId)
.collection("workouts").add(workoutMap)
.addOnSuccessListener { documentReference ->
Log.d("Firebase", "DocumentSnapshot added with ID: ${documentReference.id}")
}
.addOnFailureListener { e ->
Log.w("Firebase", "Error adding document", e)
}
}
}Choix de la solution de stockage
- Critères de sélection
- Volume et structure des données
- Performances requises
- Besoins de synchronisation
- Contraintes de confidentialité
- Tableau comparatif des solutions
- Cas d’usage
- Avantages et inconvénients
- Stratégies de stockage hybride
- Combiner plusieurs solutions
- Cache local + synchronisation cloud
Bonnes pratiques
- Sécurisation des données
- Chiffrement des données sensibles
- Protection contre les accès non autorisés
- Performances et optimisation
- Opérations asynchrones
- Mise en cache intelligente
- Gestion des versions
- Migration de schéma de base de données
- Rétrocompatibilité
Alternatives et compléments aux solutions de stockage cloud
API REST pour le stockage distant
- Principes fondamentaux
- Architecture client-serveur standardisée
- Communication via requêtes HTTP(S) (GET, POST, PUT, DELETE)
- Formats d’échange : principalement JSON, parfois XML
- Intégration avec Android
- Bibliothèques populaires : Retrofit, OkHttp, Volley
- Facilité d’intégration avec Kotlin Coroutines
- Gestion du cache et des requêtes conditionnelles
- Applications
- Synchronisation des donées avec un serveur personnel
- Intégration avec des services tiers
- Accès à des catalogues de données
Communication WebSocket pour données en temps réel
- Avantages par rapport au REST
- Communication bidirectionnelle permanente
- Réduction de la latence pour les mises à jour fréquentes
- Idéal pour les fonctionnalités collaboratives et temps réel
- Cas d’usage dans les applications fitness
- Suivi d’entraînement en direct entre coach et client
- Compétitions et défis en temps réel entre utilisateurs
- Notifications instantanées (nouveaux records, rappels)
GraphQL pour requêtes flexibles
- Concept fondamental
- Langage de requête pour API développé par Facebook
- Alternative à REST avec requêtes précises et structurées
- Client qui spécifie exactement les données dont il a besoin
- Avantages par rapport à REST
- Diminution du sur-chargement et sous-chargement de données
- Réduction du nombre de requêtes réseau (requêtes combinées)
- Introspection automatique (documentation auto-générée)
- Typage fort des données
- Intégration avec Android
- Bibliothèques clientes : Apollo GraphQL, graphql-kotlin
- Génération automatique de classes Kotlin à partir du schéma
- Support pour les fragments et les opérations complexes
Stratégies hybrides pour applications robustes
- Approche offline-first
- Stockage local principal (Room) + synchronisation cloud
- Gestion des conflits et réconciliation des données
- Expérience utilisateur fluide même sans connexion
- Architecture évolutive
- Séparation des préoccupations (repository pattern)
- Sources de données interchangeables et combinables
- Migration facilitée entre solutions de stockage
A vous de jouer !
- Apprenez en détails et mettez en Datastore et room
- https://developer.android.com/courses/android-basics-compose/unit-6?hl=fr
- En option, explorez ce que propose Firebase pour stocker les données
- https://firebase.google.com/docs/guides