shared_preferences in Flutter is the simplest way to store small pieces of non-sensitive data—things like theme mode, onboarding flags, and the last selected tab. It’s part of a broader storage toolbox alongside Hive and flutter_secure_storage, which we compare in detail in Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter.
Audience: Beginner–IntermediateTested on: Flutter 3.x, Dart 3.x, Android 14 / iOS 17, Web, macOS 14
What shared_preferences is (and is not)
shared_preferences is a small key–value storage API that persists primitive data types:
- bool, int, double
- String, List<String>
It is perfect for:
- Dark mode on/off
- “Don’t show again” checkboxes
- Last used filters or sort order
- Remembering simple onboarding progress
It is not a database and not secure storage. Tokens, passwords, and large JSON blobs do not belong here. For those, combine it with Hive and flutter_secure_storage as outlined in the comparison article above.
Installing shared_preferences
shared_preferences is an official Flutter plugin and works on mobile, web, and desktop.
# pubspec.yaml
dependencies:
shared_preferences: ^latest
Then run:
flutter pub get
Creating a small preferences service
Instead of scattering string keys everywhere, wrap shared_preferences in a dedicated service. That keeps your keys centralized and easier to refactor.
// lib/services/app_prefs.dart
import 'package:shared_preferences/shared_preferences.dart';
class AppPrefs {
AppPrefs(this._prefs);
final SharedPreferences _prefs;
static const _keyIsDarkMode = 'isDarkMode';
static const _keyHasSeenOnboarding = 'hasSeenOnboarding';
static const _keyLastEmail = 'lastEmail';
static Future<AppPrefs> load() async {
final prefs = await SharedPreferences.getInstance();
return AppPrefs(prefs);
}
bool get isDarkMode => _prefs.getBool(_keyIsDarkMode) ?? false;
Future<bool> setDarkMode(bool value) => _prefs.setBool(_keyIsDarkMode, value);
bool get hasSeenOnboarding => _prefs.getBool(_keyHasSeenOnboarding) ?? false;
Future<bool> setHasSeenOnboarding(bool value) =>
_prefs.setBool(_keyHasSeenOnboarding, value);
String? get lastEmail => _prefs.getString(_keyLastEmail);
Future<bool> setLastEmail(String value) =>
_prefs.setString(_keyLastEmail, value);
}
Using preferences in your widgets
Load AppPrefs at startup, then inject it via your DI or state management library (Provider, Riverpod, GetIt, etc.).
// main.dart (simplified)
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await AppPrefs.load();
runApp(MyApp(prefs: prefs));
}
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.prefs});
final AppPrefs prefs;
@override
Widget build(BuildContext context) {
final isDark = prefs.isDarkMode;
return MaterialApp(
theme: ThemeData(brightness: isDark ? Brightness.dark : Brightness.light),
home: HomeScreen(prefs: prefs),
);
}
}
// In a settings screen
SwitchListTile(
title: const Text('Dark mode'),
value: prefs.isDarkMode,
onChanged: (value) async {
await prefs.setDarkMode(value);
// Then trigger a rebuild via your state management solution
},
);
Async vs sync: when is it persisted?
All write methods on shared_preferences (setBool, setString, etc.) return Future<bool>. The data is also cached in memory, so reads are fast.
- Reads: cheap and synchronous after the initial
getInstance(). - Writes: asynchronous; always
awaitthem in important flows (for example, finishing onboarding) so you don’t lose data if the app kills immediately.
Common patterns with shared_preferences
Onboarding and “don’t show again” flags
Future<void> completeOnboarding(AppPrefs prefs) async {
await prefs.setHasSeenOnboarding(true);
}
On bootstrap, route users based on this flag.
Remember last used filters
Store IDs or enum names as strings. Avoid serializing whole filter objects.
// Example: sort order
enum SortOrder { newest, oldest }
extension SortOrderExt on SortOrder {
String get storageKey => name;
static SortOrder fromStorage(String? value) {
return SortOrder.values.firstWhere(
(e) => e.name == value,
orElse: () => SortOrder.newest,
);
}
}
Clearing preferences on logout
For many apps, logging out should reset non-critical preferences that are tied to the user, such as last email and onboarding state.
Future<void> onLogout(AppPrefs prefs) async {
await prefs.setLastEmail('');
await prefs.setHasSeenOnboarding(false);
// Keep some truly global settings, like theme, if you want.
}
For full wipes you can call prefs.clear(), but be careful not to remove global flags you still care about.
Security & pitfalls
- Never store secrets. shared_preferences is not encrypted. Tokens, passwords, and keys belong in secure storage (see the storage comparison article for guidance).
- Not a database. Avoid stuffing large JSON blobs or full object graphs into preferences. For structured or larger data, use Hive or another database.
- Key sprawl. Hard-coded strings in many files make refactors painful. Keep all keys in one service or constants file.
- Over-aggressive clearing. Calling
clear()can wipe analytics opt-in flags or one-time dismissals. Consider a “per user” subset of keys to reset instead.
FAQ
Q: Can I use shared_preferences on the web?
A: Yes. On web it uses local storage under the hood. Just remember it’s still not secure and can be cleared by the browser.
Q: Is there a size limit?
A: It’s intended for small values. While there’s no strict Dart API limit, platforms may enforce practical limits. If you’re storing megabytes of data, you’re misusing it—use Hive or another database instead.
Q: Should I wrap it with a reactive layer?
A: For most apps, it’s enough to update your state management layer when you write preferences. If you need live updates from outside the app (rare), consider an abstraction that exposes a stream when you change values.
Conclusion
shared_preferences in Flutter shines when you keep it small and focused: a few booleans, strings, and simple lists that shape the user experience but don’t need strong security. Combine it with Hive for structured data and flutter_secure_storage for secrets—as described in the comparison article—to build a clean, layered storage strategy that scales with your app.
Updated: 2025-11-12


Comment