Stratégies de Stockage Flutter
Alternatives de Bases de Données Locales
Section titled “Alternatives de Bases de Données Locales”Drift (anciennement Moor)
Section titled “Drift (anciennement Moor)”Drift est un ORM puissant pour SQLite avec génération de code type-safe.
dependencies: drift: ^2.14.1 sqlite3_flutter_libs: ^0.5.15 path_provider: ^2.0.15 path: ^1.8.3
dev_dependencies: drift_dev: ^2.14.1 build_runner: ^2.4.7Exemple de table :
import 'package:drift/drift.dart';
@DataClassName('Todo')class Todos extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text().withLength(min: 1, max: 50)(); TextColumn get content => text().named('body')(); IntColumn get category => integer().nullable()(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();}
@DriftDatabase(tables: [Todos])class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection());
@override int get schemaVersion => 1;
// Requêtes Future<List<Todo>> getAllTodos() => select(todos).get(); Future<Todo> getTodoById(int id) => (select(todos)..where((t) => t.id.equals(id))).getSingle(); Future<int> insertTodo(TodosCompanion todo) => into(todos).insert(todo); Future<bool> updateTodo(Todo todo) => update(todos).replace(todo); Future<int> deleteTodo(int id) => (delete(todos)..where((t) => t.id.equals(id))).go();}Base de données NoSQL rapide et légère, parfaite pour le stockage d’objets.
dependencies: hive: ^2.2.3 hive_flutter: ^1.1.0
dev_dependencies: hive_generator: ^2.0.1 build_runner: ^2.4.7Configuration :
import 'package:hive_flutter/hive_flutter.dart';
@HiveType(typeId: 0)class User extends HiveObject { @HiveField(0) String name;
@HiveField(1) int age;
@HiveField(2) List<String> hobbies;
User({required this.name, required this.age, required this.hobbies});}
// Initialisationawait Hive.initFlutter();Hive.registerAdapter(UserAdapter());
// Utilisationfinal box = await Hive.openBox<User>('users');
// CRUDawait box.add(User(name: 'John', age: 25, hobbies: ['Reading']));final users = box.values.toList();await box.putAt(0, User(name: 'Jane', age: 30, hobbies: ['Swimming']));await box.deleteAt(0);Base de données ultra-rapide avec support des requêtes complexes.
dependencies: isar: ^3.1.0+1 isar_flutter_libs: ^3.1.0+1
dev_dependencies: isar_generator: ^3.1.0+1 build_runner: ^2.4.7Exemple :
import 'package:isar/isar.dart';
@collectionclass Contact { Id id = Isar.autoIncrement;
@Index(type: IndexType.value) String? name;
String? phone;
@Index() List<String> tags = [];}
// Initialisationfinal isar = await Isar.open([ContactSchema]);
// CRUDawait isar.writeTxn(() async { await isar.contacts.put(Contact()..name = 'John'..phone = '123-456-7890');});
final contacts = await isar.contacts.where().nameContains('John').findAll();ObjectBox
Section titled “ObjectBox”Base de données orientée objet avec synchronisation.
dependencies: objectbox: ^2.3.1 objectbox_flutter_libs: ^2.3.1
dev_dependencies: objectbox_generator: ^2.3.1 build_runner: ^2.4.7Stratégies de Persistance de Données
Section titled “Stratégies de Persistance de Données”Choix de la Solution
Section titled “Choix de la Solution”-
SharedPreferences :
- Préférences utilisateur
- Paramètres simples
- Tokens non-sensibles
- Données < 1MB
-
Flutter Secure Storage :
- Tokens d’authentification
- Mots de passe
- Clés API
- Données sensibles
-
SQLite/sqflite :
- Données relationnelles
- Requêtes complexes
- Intégrité référentielle
- Applications CRUD
-
Hive :
- Données non-relationnelles
- Performance élevée
- Objects Dart directs
- Cache local
-
Isar/ObjectBox :
- Applications complexes
- Requêtes avancées
- Synchronisation
- Performance critique
Architecture Recommandée
Section titled “Architecture Recommandée”abstract class LocalDataSource<T> { Future<List<T>> getAll(); Future<T?> getById(String id); Future<void> insert(T item); Future<void> update(T item); Future<void> delete(String id); Future<void> clear();}
class UserLocalDataSource implements LocalDataSource<User> { final UserRepository _repository = UserRepository();
@override Future<List<User>> getAll() => _repository.getAllUsers();
@override Future<User?> getById(String id) => _repository.getUserById(int.parse(id));
@override Future<void> insert(User user) async { await _repository.insertUser(user); }
@override Future<void> update(User user) async { await _repository.updateUser(user); }
@override Future<void> delete(String id) async { await _repository.deleteUser(int.parse(id)); }
@override Future<void> clear() async { // Implémentation de la suppression de tous les utilisateurs }}Pattern Repository avec Multiple Storage
Section titled “Pattern Repository avec Multiple Storage”abstract class DataRepository<T> { Future<List<T>> getAll(); Future<T?> getById(String id); Future<void> save(T item); Future<void> delete(String id);}
class UserRepository implements DataRepository<User> { final LocalDataSource<User> _localDataSource; final RemoteDataSource<User> _remoteDataSource; final CacheDataSource<User> _cacheDataSource;
UserRepository({ required LocalDataSource<User> localDataSource, required RemoteDataSource<User> remoteDataSource, required CacheDataSource<User> cacheDataSource, }) : _localDataSource = localDataSource, _remoteDataSource = remoteDataSource, _cacheDataSource = cacheDataSource;
@override Future<List<User>> getAll() async { try { // Essayer le cache d'abord final cachedUsers = await _cacheDataSource.getAll(); if (cachedUsers.isNotEmpty) { return cachedUsers; }
// Ensuite la base locale final localUsers = await _localDataSource.getAll(); if (localUsers.isNotEmpty) { await _cacheDataSource.saveAll(localUsers); return localUsers; }
// Finalement le serveur final remoteUsers = await _remoteDataSource.getAll(); await _localDataSource.saveAll(remoteUsers); await _cacheDataSource.saveAll(remoteUsers);
return remoteUsers; } catch (e) { // Fallback sur le local en cas d'erreur return await _localDataSource.getAll(); } }}Critères de Sélection
Section titled “Critères de Sélection”| Solution | Performance | Complexité | Taille données | Relations | Sécurité |
|---|---|---|---|---|---|
| SharedPreferences | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | < 1MB | ❌ | ⭐ |
| Secure Storage | ⭐⭐ | ⭐⭐⭐⭐ | < 100KB | ❌ | ⭐⭐⭐⭐⭐ |
| SQLite | ⭐⭐⭐ | ⭐⭐ | Illimitée | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Hive | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Grande | ⭐⭐ | ⭐⭐ |
| Isar | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Très grande | ⭐⭐⭐⭐ | ⭐⭐ |
| ObjectBox | ⭐⭐⭐⭐ | ⭐⭐⭐ | Très grande | ⭐⭐⭐⭐ | ⭐⭐ |
Stratégies de Migration
Section titled “Stratégies de Migration”Migration SQLite vers Drift
Section titled “Migration SQLite vers Drift”class MigrationService { static Future<void> migrateFromSQLiteToHive() async { // 1. Lire les données existantes de SQLite final sqliteData = await SQLiteRepository().getAllUsers();
// 2. Initialiser Hive await Hive.initFlutter(); final hiveBox = await Hive.openBox<User>('users');
// 3. Migrer les données for (final user in sqliteData) { await hiveBox.add(user); }
// 4. Marquer la migration comme terminée final prefs = await SharedPreferences.getInstance(); await prefs.setBool('migrated_to_hive', true); }
static Future<bool> needsMigration() async { final prefs = await SharedPreferences.getInstance(); return !(prefs.getBool('migrated_to_hive') ?? false); }}Bonnes Pratiques
Section titled “Bonnes Pratiques”- Sécurité : Toujours chiffrer les données sensibles
- Performance : Utiliser des index pour les requêtes fréquentes
- Migrations : Planifier les changements de schéma
- Sauvegarde : Implémenter des mécanismes de backup
- Tests : Tester toutes les opérations de persistance
- Architecture : Séparer les couches de données
- Monitoring : Surveiller les performances de stockage
Patterns de Stockage Hybride
Section titled “Patterns de Stockage Hybride”class HybridStorageService { final SharedPreferences _prefs; final FlutterSecureStorage _secureStorage; final Database _database; final Box _hiveBox;
HybridStorageService({ required SharedPreferences prefs, required FlutterSecureStorage secureStorage, required Database database, required Box hiveBox, }) : _prefs = prefs, _secureStorage = secureStorage, _database = database, _hiveBox = hiveBox;
// Configuration non-sensible Future<void> saveUserPreference(String key, dynamic value) async { if (value is bool) await _prefs.setBool(key, value); if (value is int) await _prefs.setInt(key, value); if (value is String) await _prefs.setString(key, value); }
// Données sensibles Future<void> saveSecureData(String key, String value) async { await _secureStorage.write(key: key, value: value); }
// Données relationnelles Future<void> saveRelationalData(String table, Map<String, dynamic> data) async { await _database.insert(table, data); }
// Cache haute performance Future<void> saveCacheData(String key, dynamic data) async { await _hiveBox.put(key, data); }}Cette approche modulaire permet de maintenir un code propre et de faciliter les changements de solutions de stockage selon l’évolution des besoins de l’application.