Persist preferences and lightweight data with shared_preferences while knowing its security and volume limits.
Open this lesson in Kodokonshared_preferences is a key/value store backed by native mechanisms (SharedPreferences on Android, UserDefaults on iOS). Its sweet spot: small data - chosen theme, onboarding seen, last active tab. Beyond that, switch tools: a sqflite or drift database for large or queryable data, flutter_secure_storage for anything sensitive. The file is loaded entirely into memory: it is neither encrypted nor built to grow.
flutter pub add shared_preferencesgetInstance() is asynchronous on the first call (reading the file), then returns a cached instance: the subsequent get* calls are synchronous, served from memory. The set* calls, on the other hand, write to disk asynchronously. In practice, don't scatter key strings all over the application: centralize them in a typed, injectable, testable wrapper.
import 'package:shared_preferences/shared_preferences.dart';
class SettingsStore {
SettingsStore(this._prefs);
static const _darkModeKey = 'settings.darkMode';
final SharedPreferences _prefs;
static Future<SettingsStore> load() async {
final prefs = await SharedPreferences.getInstance();
return SettingsStore(prefs);
}
bool get darkMode =>
_prefs.getBool(_darkModeKey) ?? false;
Future<void> setDarkMode(bool value) =>
_prefs.setBool(_darkModeKey, value);
}The API only stores primitive types: bool, int, double, String, and List<String>. For a structured object, serialize it to JSON - your toJson/fromJson from the first lesson come in handy again here. An accepted trade-off: each save rewrites the entire value and no querying is possible. Perfect for a draft, unsuited to a history.
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Draft {
const Draft({required this.title, required this.body});
factory Draft.fromJson(Map<String, dynamic> json) =>
Draft(
title: json['title'] as String,
body: json['body'] as String,
);
final String title;
final String body;
Map<String, dynamic> toJson() =>
{'title': title, 'body': body};
}
Future<void> saveDraft(Draft draft) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('draft', jsonEncode(draft));
}
Future<Draft?> loadDraft() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString('draft');
if (raw == null) return null;
return Draft.fromJson(
jsonDecode(raw) as Map<String, dynamic>,
);
}shared_preferences?getInstance(), how do the get* reads behave?Futures you must await every time.Draft object?jsonEncode and store it via setString.prefs.setObject('draft', draft) designed for this purpose.setStringList.