Storage (shared_prefs / sqflite)
Persistent storage in Flutter: shared_preferences for tiny key/value, secure_storage for credentials, sqflite or Drift for relational data, Hive or ObjectBox for fast key/value with type safety, and the file system via path_provider for anything else.
shared_preferences, secure_storage, sqflite
EXAMPLE
// pubspec.yaml
// dependencies:
// shared_preferences: ^2.2.0
// flutter_secure_storage: ^9.0.0
// sqflite: ^2.3.0
// path: ^1.9.0
// path_provider: ^2.1.0
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:convert';
// ===== 1) Shared preferences — tiny key/value =====
class Prefs {
static const _kTheme = 'theme';
static const _kSort = 'sort';
static Future<String> getTheme() async {
final sp = await SharedPreferences.getInstance();
return sp.getString(_kTheme) ?? 'system';
}
static Future<void> setTheme(String value) async {
final sp = await SharedPreferences.getInstance();
await sp.setString(_kTheme, value);
}
static Future<Map<String, dynamic>> all() async {
final sp = await SharedPreferences.getInstance();
return { for (final k in sp.getKeys()) k: sp.get(k) };
}
}
// ===== 2) Secure storage — credentials, OAuth tokens =====
class Secure {
static const _storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
static Future<void> setToken(String token) =>
_storage.write(key: 'access_token', value: token);
static Future<String?> getToken() => _storage.read(key: 'access_token');
static Future<void> clear() => _storage.deleteAll();
}
// iOS uses Keychain, Android EncryptedSharedPreferences. NEVER store tokens in
// shared_preferences — backups & rooted devices can read them.
// ===== 3) sqflite — relational storage for non-trivial data =====
class Db {
static Database? _db;
static Future<Database> get instance async {
if (_db != null) return _db!;
final dir = await getApplicationDocumentsDirectory();
final path = p.join(dir.path, 'shop.db');
_db = await openDatabase(
path,
version: 2,
onCreate: (db, v) async {
await db.execute('''
CREATE TABLE orders (
id TEXT PRIMARY KEY,
customer TEXT NOT NULL,
total_cents INTEGER NOT NULL,
status TEXT NOT NULL,
created_at INTEGER NOT NULL
)
''');
await db.execute('CREATE INDEX ix_orders_created ON orders(created_at)');
},
onUpgrade: (db, oldV, newV) async {
if (oldV < 2) await db.execute('ALTER TABLE orders ADD COLUMN notes TEXT');
},
);
return _db!;
}
static Future<void> insert(Order o) async {
final db = await instance;
await db.insert('orders', {
'id': o.id, 'customer': o.customer, 'total_cents': o.totalCents,
'status': o.status, 'created_at': o.createdAt.millisecondsSinceEpoch,
}, conflictAlgorithm: ConflictAlgorithm.replace);
}
static Future<List<Order>> list({String? status}) async {
final db = await instance;
final rows = await db.query('orders',
where: status == null ? null : 'status = ?',
whereArgs: status == null ? null : [status],
orderBy: 'created_at DESC',
limit: 200);
return rows.map(Order.fromRow).toList();
}
static Future<void> wipe() async {
final db = await instance;
await db.delete('orders');
}
}
class Order {
Order({required this.id, required this.customer, required this.totalCents,
required this.status, required this.createdAt});
final String id, customer, status;
final int totalCents;
final DateTime createdAt;
static Order fromRow(Map<String, Object?> r) => Order(
id: r['id'] as String,
customer: r['customer'] as String,
totalCents: r['total_cents'] as int,
status: r['status'] as String,
createdAt: DateTime.fromMillisecondsSinceEpoch(r['created_at'] as int),
);
}
// ===== 4) File-system storage — JSON snapshots, images, downloads =====
class Files {
static Future<File> _file(String name) async {
final dir = await getApplicationDocumentsDirectory();
return File(p.join(dir.path, name));
}
static Future<void> writeJson(String name, Object data) async {
final f = await _file(name);
await f.writeAsString(jsonEncode(data));
}
static Future<Map<String, dynamic>?> readJson(String name) async {
final f = await _file(name);
if (!await f.exists()) return null;
return jsonDecode(await f.readAsString()) as Map<String, dynamic>;
}
}
// ===== 5) Decision matrix =====
// - Tiny prefs (theme, sort) -> shared_preferences
// - Tokens, secrets -> flutter_secure_storage
// - Tabular data (offline list / cache) -> sqflite / Drift
// - Key-value with types + queries -> Hive / ObjectBox / Isar
// - Large blobs (images, audio) -> File system via path_provider
// - Cross-platform sync -> Firebase / your API + sqflite cache
// ===== 6) Pitfalls =====
// - Storing tokens in shared_preferences (insecure)
// - Doing DB work on the main isolate without await
// - Forgetting onUpgrade -> users on old versions hit schema mismatches
// - Storing big binary blobs in sqflite (use the file system)
// - Not closing the database in tests (sqflite keeps a singleton)
Why it matters
Use the right storage for the data shape: tokens in flutter_secure_storage, prefs in shared_preferences, structured data in sqflite/Drift, blobs on the file system. The single most common Flutter bug is putting auth tokens in shared_preferences — they survive uninstall on some platforms and are readable from device backups. Get this one habit right.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// shared_preferences (key/value)
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 't0k3n');
final v = prefs.getString('token');
Try it Yourself »
Discussion
Loading…