Skip to content

Stockage Local Flutter

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 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.

dependencies:
shared_preferences: ^2.2.2
import 'package:shared_preferences/shared_preferences.dart';
// Obtenir une instance de SharedPreferences
final prefs = await SharedPreferences.getInstance();
// Sauvegarder différents types de données
await 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ées
final 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ées
final success = await prefs.remove('counter');
// Nettoyer toutes les données
await prefs.clear();
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);
}
}

Pour des données plus complexes et des relations entre entités, SQLite est la solution recommandée.

dependencies:
sqflite: ^2.3.0
path: ^1.8.3
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)
)
''');
}
}
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],
);
}
}
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],
);
});
}

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.

dependencies:
flutter_secure_storage: ^9.0.0

Dans android/app/build.gradle :

android {
compileSdkVersion 33
// ...
}
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();
}
}

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.7

Exemple 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.7

Configuration :

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});
}
// Initialisation
await Hive.initFlutter();
Hive.registerAdapter(UserAdapter());
// Utilisation
final box = await Hive.openBox<User>('users');
// CRUD
await 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.7

Exemple :

import 'package:isar/isar.dart';
@collection
class Contact {
Id id = Isar.autoIncrement;
@Index(type: IndexType.value)
String? name;
String? phone;
@Index()
List<String> tags = [];
}
// Initialisation
final isar = await Isar.open([ContactSchema]);
// CRUD
await isar.writeTxn(() async {
await isar.contacts.put(Contact()..name = 'John'..phone = '123-456-7890');
});
final contacts = await isar.contacts.where().nameContains('John').findAll();

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.7
  1. SharedPreferences :

    • Préférences utilisateur
    • Paramètres simples
    • Tokens non-sensibles
    • Données < 1MB
  2. Flutter Secure Storage :

    • Tokens d’authentification
    • Mots de passe
    • Clés API
    • Données sensibles
  3. SQLite/sqflite :

    • Données relationnelles
    • Requêtes complexes
    • Intégrité référentielle
    • Applications CRUD
  4. Hive :

    • Données non-relationnelles
    • Performance élevée
    • Objects Dart directs
    • Cache local
  5. Isar/ObjectBox :

    • Applications complexes
    • Requêtes avancées
    • Synchronisation
    • Performance critique
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
}
}
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');
}
}
}
  1. Sécurité : Toujours chiffrer les données sensibles
  2. Performance : Utiliser des index pour les requêtes fréquentes
  3. Migrations : Planifier les changements de schéma
  4. Sauvegarde : Implémenter des mécanismes de backup
  5. 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.