2025-03-12
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 | 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é |
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
Questions clés à se poser :
// Récupérer les SharedPreferences
val sharedPref = getSharedPreferences("fitness_app", Context.MODE_PRIVATE)
// Écriture de données
val editor = sharedPref.edit()
editor.putString("username", "JohnDoe")
editor.putBoolean("notifications", true)
editor.putInt("daily_steps_goal", 10000)
editor.apply()
// Lecture de données
val username = sharedPref.getString("username", "")
val notificationsEnabled = sharedPref.getBoolean("notifications", false)
val stepsGoal = sharedPref.getInt("daily_steps_goal", 8000)
Successeur moderne de SharedPreferences
Solution de stockage de données développée par Google
Partie de la bibliothèque Android Jetpack
API entièrement basée sur Kotlin Coroutines
Deux implémentations
Avantages par rapport à SharedPreferences
// 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
}
}
Définition
/data/data/[package_name]/files/
Avantages
Limitations
Non visible via gestionnaire de fichiers
Non partageable facilement
Limité à la durée de vie de l’app
Cas d’usage
// É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 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
/Android/data/[package_name]/
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 |
// 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()
}
}
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 |
// 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
}
// 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)
}
}
}
E. Bruno - Stockage des données sur Android