Stockage Local Flutter
Persistance Locale
Section titled “Persistance Locale”La persistance des données est un aspect crucial du développement d’applications mobiles. Flutter offre plusieurs solutions pour stocker des données localement, chacune adaptée à différents besoins et types de données.
SharedPreferences
Section titled “SharedPreferences”SharedPreferences est idéal pour stocker de petites quantités de données simples comme les préférences utilisateur, les tokens d’authentification, ou les configurations d’application.
Installation
Section titled “Installation”dependencies: shared_preferences: ^2.2.2Utilisation
Section titled “Utilisation”import 'package:shared_preferences/shared_preferences.dart';
// Obtenir une instance de SharedPreferencesfinal prefs = await SharedPreferences.getInstance();
// Sauvegarder différents types de donnéesawait prefs.setInt('counter', 10);await prefs.setString('action', 'Start');await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);await prefs.setBool('isDarkMode', true);await prefs.setDouble('rating', 4.5);
// Lire les donnéesfinal int? counter = prefs.getInt('counter');final bool? repeat = prefs.getBool('repeat');final double? decimal = prefs.getDouble('decimal');final String? action = prefs.getString('action');final List<String>? items = prefs.getStringList('items');
// Supprimer des donnéesfinal success = await prefs.remove('counter');
// Nettoyer toutes les donnéesawait prefs.clear();Bonnes Pratiques
Section titled “Bonnes Pratiques”class PreferencesService { static const String _keyCounter = 'counter'; static const String _keyUsername = 'username';
static Future<SharedPreferences> get _prefs async => await SharedPreferences.getInstance();
static Future<int> getCounter() async { final prefs = await _prefs; return prefs.getInt(_keyCounter) ?? 0; }
static Future<void> setCounter(int value) async { final prefs = await _prefs; await prefs.setInt(_keyCounter, value); }
static Future<String?> getUsername() async { final prefs = await _prefs; return prefs.getString(_keyUsername); }
static Future<void> setUsername(String username) async { final prefs = await _prefs; await prefs.setString(_keyUsername, username); }}SQLite avec sqflite
Section titled “SQLite avec sqflite”Pour des données plus complexes et des relations entre entités, SQLite est la solution recommandée.
Installation
Section titled “Installation”dependencies: sqflite: ^2.3.0 path: ^1.8.3Configuration de Base
Section titled “Configuration de Base”import 'package:sqflite/sqflite.dart';import 'package:path/path.dart';
class DatabaseHelper { static final DatabaseHelper _instance = DatabaseHelper._internal(); factory DatabaseHelper() => _instance; DatabaseHelper._internal();
Database? _database;
Future<Database> get database async { _database ??= await _initDatabase(); return _database!; }
Future<Database> _initDatabase() async { String path = join(await getDatabasesPath(), 'app_database.db');
return await openDatabase( path, version: 1, onCreate: _onCreate, ); }
Future<void> _onCreate(Database db, int version) async { await db.execute(''' CREATE TABLE users( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, created_at TEXT NOT NULL ) ''');
await db.execute(''' CREATE TABLE tasks( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, completed INTEGER NOT NULL DEFAULT 0, user_id INTEGER, FOREIGN KEY (user_id) REFERENCES users (id) ) '''); }}Opérations CRUD
Section titled “Opérations CRUD”class User { final int? id; final String name; final String email; final DateTime createdAt;
User({this.id, required this.name, required this.email, required this.createdAt});
Map<String, dynamic> toMap() { return { 'id': id, 'name': name, 'email': email, 'created_at': createdAt.toIso8601String(), }; }
factory User.fromMap(Map<String, dynamic> map) { return User( id: map['id'], name: map['name'], email: map['email'], createdAt: DateTime.parse(map['created_at']), ); }}
class UserRepository { final DatabaseHelper _dbHelper = DatabaseHelper();
Future<int> insertUser(User user) async { final db = await _dbHelper.database; return await db.insert('users', user.toMap()); }
Future<List<User>> getAllUsers() async { final db = await _dbHelper.database; final List<Map<String, dynamic>> maps = await db.query('users');
return List.generate(maps.length, (i) => User.fromMap(maps[i])); }
Future<User?> getUserById(int id) async { final db = await _dbHelper.database; final List<Map<String, dynamic>> maps = await db.query( 'users', where: 'id = ?', whereArgs: [id], );
if (maps.isNotEmpty) { return User.fromMap(maps.first); } return null; }
Future<int> updateUser(User user) async { final db = await _dbHelper.database; return await db.update( 'users', user.toMap(), where: 'id = ?', whereArgs: [user.id], ); }
Future<int> deleteUser(int id) async { final db = await _dbHelper.database; return await db.delete( 'users', where: 'id = ?', whereArgs: [id], ); }}Transactions
Section titled “Transactions”Future<void> transferTasks(int fromUserId, int toUserId) async { final db = await DatabaseHelper().database;
await db.transaction((txn) async { // Vérifier que les utilisateurs existent final fromUser = await txn.query('users', where: 'id = ?', whereArgs: [fromUserId]); final toUser = await txn.query('users', where: 'id = ?', whereArgs: [toUserId]);
if (fromUser.isEmpty || toUser.isEmpty) { throw Exception('Utilisateur non trouvé'); }
// Transférer les tâches await txn.update( 'tasks', {'user_id': toUserId}, where: 'user_id = ?', whereArgs: [fromUserId], ); });}Flutter Secure Storage
Section titled “Flutter Secure Storage”Pour stocker des données sensibles comme les tokens d’authentification, mots de passe, clés API, utilisez Flutter Secure Storage qui chiffre les données.
Installation
Section titled “Installation”dependencies: flutter_secure_storage: ^9.0.0Configuration Android
Section titled “Configuration Android”Dans android/app/build.gradle :
android { compileSdkVersion 33 // ...}Utilisation
Section titled “Utilisation”import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService { static const _storage = FlutterSecureStorage( aOptions: AndroidOptions( encryptedSharedPreferences: true, ), iOptions: IOSOptions( accessibility: KeychainAccessibility.first_unlock_this_device, ), );
// Sauvegarder des données sensibles static Future<void> storeToken(String token) async { await _storage.write(key: 'auth_token', value: token); }
static Future<void> storeCredentials(String username, String password) async { await _storage.write(key: 'username', value: username); await _storage.write(key: 'password', value: password); }
// Lire des données static Future<String?> getToken() async { return await _storage.read(key: 'auth_token'); }
static Future<Map<String, String?>>getCredentials() async { final username = await _storage.read(key: 'username'); final password = await _storage.read(key: 'password'); return {'username': username, 'password': password}; }
// Supprimer des données static Future<void> deleteToken() async { await _storage.delete(key: 'auth_token'); }
static Future<void> deleteAll() async { await _storage.deleteAll(); }
// Vérifier l'existence d'une clé static Future<bool> hasToken() async { return await _storage.containsKey(key: 'auth_token'); }
// Lister toutes les clés static Future<Map<String, String>> getAllData() async { return await _storage.readAll(); }}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 }}Gestion des Migrations
Section titled “Gestion des Migrations”class DatabaseHelper { Future<Database> _initDatabase() async { return await openDatabase( path, version: 2, // Version mise à jour onCreate: _onCreate, onUpgrade: _onUpgrade, ); }
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { await db.execute('ALTER TABLE users ADD COLUMN avatar_url TEXT'); } }}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
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.