Every meaningful app needs to remember something between sessions. Whether it is a user's theme preference, a cached list of items, or a full relational dataset, Flutter provides a clear set of tools for every persistence need.
Choosing the Right Storage Solution
What kind of data do you need to store?
|
+-- Simple key-value pairs (settings, flags)
| --> Shared Preferences
|
+-- Structured relational data (tables, queries)
| --> SQLite (sqflite)
|
+-- Fast, lightweight NoSQL data
| --> Hive
|
+-- Files (images, documents, downloads)
| --> dart:io file system
|
+-- Client-side web storage
--> IndexedDB (web)
Shared Preferences
Shared Preferences stores simple key-value pairs persistently on the device. It maps to SharedPreferences on Android and NSUserDefaults on iOS.
Best suited for:
- User settings (theme, language, notification preferences)
- App configuration flags
- Small amounts of primitive data (strings, booleans, integers, doubles)
flutter pub add shared_preferences
import 'package:shared_preferences/shared_preferences.dart';
class SettingsService {
// Save data
static Future<void> saveThemeMode(bool isDark) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('dark_mode', isDark);
}
// Read data
static Future<bool> loadThemeMode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool('dark_mode') ?? false;
}
// Delete data
static Future<void> clearAll() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
}
}
Shared Preferences supports the following types:
| Method | Type |
|---|---|
setString / getString | String |
setInt / getInt | int |
setDouble / getDouble | double |
setBool / getBool | bool |
setStringList / getStringList | List<String> |
SQLite (sqflite)
SQLite is an embedded relational database. Use it for complex data that requires queries, joins, filtering, and ordering.
flutter pub add sqflite
flutter pub add path
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static Database? _database;
static Future<Database> getDatabase() async {
if (_database != null) return _database!;
final path = join(await getDatabasesPath(), 'app_database.db');
_database = await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
firstName TEXT NOT NULL,
lastName TEXT NOT NULL,
age INTEGER,
major TEXT
)
''');
},
);
return _database!;
}
// CREATE
static Future<int> insertStudent(Map<String, dynamic> student) async {
final db = await getDatabase();
return await db.insert('students', student);
}
// READ
static Future<List<Map<String, dynamic>>> getAllStudents() async {
final db = await getDatabase();
return await db.query('students', orderBy: 'lastName ASC');
}
// UPDATE
static Future<int> updateStudent(Map<String, dynamic> student) async {
final db = await getDatabase();
return await db.update(
'students',
student,
where: 'id = ?',
whereArgs: [student['id']],
);
}
// DELETE
static Future<int> deleteStudent(int id) async {
final db = await getDatabase();
return await db.delete('students', where: 'id = ?', whereArgs: [id]);
}
}
Hive
Hive is a lightweight, fast NoSQL database written entirely in Dart. It stores data in a type-safe, binary format and is significantly faster than SQLite for simple read/write operations.
flutter pub add hive
flutter pub add hive_flutter
import 'package:hive_flutter/hive_flutter.dart';
// Initialize Hive
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
await Hive.openBox('settings');
runApp(const MyApp());
}
// Use the box
final box = Hive.box('settings');
// Write
box.put('username', 'amr_tarek');
box.put('score', 9850);
// Read
final username = box.get('username', defaultValue: 'Guest');
final score = box.get('score', defaultValue: 0);
// Delete
box.delete('username');
For storing custom Dart objects, Hive uses TypeAdapters (generated with hive_generator).
Direct File Management
For downloading and saving media files, documents, or any binary data, use the dart:io file system directly.
flutter pub add path_provider
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class FileService {
// Get the app's documents directory
static Future<Directory> getDocumentsDir() async {
return await getApplicationDocumentsDirectory();
}
// Write a text file
static Future<File> writeFile(String filename, String content) async {
final dir = await getDocumentsDir();
final file = File('${dir.path}/$filename');
return await file.writeAsString(content);
}
// Read a text file
static Future<String> readFile(String filename) async {
final dir = await getDocumentsDir();
final file = File('${dir.path}/$filename');
if (await file.exists()) {
return await file.readAsString();
}
return '';
}
// Delete a file
static Future<void> deleteFile(String filename) async {
final dir = await getDocumentsDir();
final file = File('${dir.path}/$filename');
if (await file.exists()) {
await file.delete();
}
}
}
Serialization and Deserialization
When working with databases or APIs, you need to convert between Dart objects and JSON/Map formats.
Defining a Model with Serialization
class Student {
final int? id;
final String firstName;
final String lastName;
final int age;
final String major;
Student({
this.id,
required this.firstName,
required this.lastName,
required this.age,
required this.major,
});
// Deserialize from a Map (e.g., from SQLite or JSON API)
factory Student.fromJson(Map<String, dynamic> json) {
return Student(
id: json['id'],
firstName: json['firstName'],
lastName: json['lastName'],
age: json['age'],
major: json['major'],
);
}
// Serialize to a Map (e.g., for SQLite insert or JSON encoding)
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'firstName': firstName,
'lastName': lastName,
'age': age,
'major': major,
};
}
}
Using dart:convert for JSON:
import 'dart:convert';
// Decode JSON string to Dart Map
final Map<String, dynamic> userMap = jsonDecode(jsonString);
final user = Student.fromJson(userMap);
// Encode Dart object to JSON string
final String encoded = jsonEncode(user.toJson());
CRUD with State Management
Combine persistence with Provider for a complete data management layer.
import 'package:flutter/foundation.dart';
class StudentProvider with ChangeNotifier {
List<Student> _students = [];
List<Student> get students => List.unmodifiable(_students);
void addStudent(Student student) {
_students.add(student);
_saveToStorage();
notifyListeners();
}
void removeStudent(int id) {
_students.removeWhere((s) => s.id == id);
_saveToStorage();
notifyListeners();
}
void updateStudent(Student updated) {
final index = _students.indexWhere((s) => s.id == updated.id);
if (index != -1) {
_students[index] = updated;
_saveToStorage();
notifyListeners();
}
}
void _saveToStorage() {
// Persist to Shared Preferences, SQLite, or Hive
}
}
Storage Comparison
| Solution | Type | Best For | Speed |
|---|---|---|---|
| Shared Preferences | Key-value | Settings, flags | Fast |
| SQLite (sqflite) | Relational | Complex structured data | Medium |
| Hive | NoSQL | Objects, fast reads | Very fast |
| Files | Binary/Text | Media, documents | Variable |
| IndexedDB | Key-value | Web client storage | Fast |
The right choice depends on your data shape, query requirements, and performance needs. Many apps use a combination — Shared Preferences for settings, Hive or SQLite for domain data, and the file system for media.