Flutter offers several local persistence options ranging from simple key-value stores to full relational databases.
SharedPreferences — simple key-value storage
Best for: settings, flags, small strings, tokens.
dependencies:
shared_preferences: ^2.2.0import 'package:shared_preferences/shared_preferences.dart';
// Write
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 'abc123');
await prefs.setBool('dark_mode', true);
await prefs.setInt('launch_count', 5);
// Read
final token = prefs.getString('token') ?? '';
final darkMode = prefs.getBool('dark_mode') ?? false;
final launches = prefs.getInt('launch_count') ?? 0;
// Remove
await prefs.remove('token');
// Clear all
await prefs.clear();Hive — fast NoSQL database
Best for: structured data without needing SQL. Very fast, works on all platforms.
dependencies:
hive: ^2.2.0
hive_flutter: ^1.1.0import 'package:hive_flutter/hive_flutter.dart';
// Initialize
await Hive.initFlutter();
// Open a box
final box = await Hive.openBox('settings');
// Write
await box.put('theme', 'dark');
await box.put('fontSize', 16);
// Read
final theme = box.get('theme', defaultValue: 'light');
final fontSize = box.get('fontSize', defaultValue: 14);
// Custom objects — register adapter
@HiveType(typeId: 0)
class User extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
int age;
User({required this.name, required this.age});
}
// After generating adapter:
// flutter packages pub run build_runner build
await Hive.openBox<User>('users');
await box.add(User(name: 'Alice', age: 30));sqflite — SQLite database
Best for: relational data, complex queries, structured schemas.
dependencies:
sqflite: ^2.3.0
path: ^1.9.0import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> initDb() async {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'my_app.db'),
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT
)
''');
},
);
}
// Insert
Future<void> insertUser(Database db, User user) async {
await db.insert('users', user.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
// Query
Future<List<User>> getUsers(Database db) async {
final maps = await db.query('users', orderBy: 'name ASC');
return maps.map((m) => User.fromMap(m)).toList();
}
// Update
Future<void> updateUser(Database db, User user) async {
await db.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]);
}
// Delete
Future<void> deleteUser(Database db, int id) async {
await db.delete('users', where: 'id = ?', whereArgs: [id]);
}Drift — type-safe SQLite (recommended for production)
Drift (formerly Moor) provides type-safe access to SQLite with compile-time query verification.
dependencies:
drift: ^2.15.0
sqlite3_flutter_libs: ^0.5.0
dev_dependencies:
drift_dev: ^2.15.0
build_runner: ^2.4.0import 'package:drift/drift.dart';
import 'package:drift/native.dart';
// Define tables
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get email => text().nullable()();
}
// Define database
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(NativeDatabase.memory());
@override
int get schemaVersion => 1;
// Queries
Future<List<User>> getAllUsers() => select(users).get();
Stream<List<User>> watchAllUsers() => select(users).watch();
Future<void> insertUser(UsersCompanion user) => into(users).insert(user);
Future<void> updateUser(User user) => update(users).replace(user);
Future<void> deleteUser(int id) =>
(delete(users)..where((u) => u.id.equals(id))).go();
}Run code generation: dart run build_runner build.
Which should you use?
| Solution | Best for | Complexity | Relational |
|---|---|---|---|
| SharedPreferences | Settings, tokens, flags | Low | No |
| Hive | Structured non-relational data | Medium | No |
| sqflite | SQL queries, relational data | Medium | Yes |
| Drift | Type-safe SQLite, production apps | Higher | Yes |