SharedPreferences: Storing Simple Key-Value Data
SharedPreferences: Storing Simple Key-Value Data
Most apps need to persist small pieces of data between sessions: whether the user has completed onboarding, their chosen theme, a saved username, or a preference toggle. SharedPreferences is the standard Flutter solution for this use case. Backed by NSUserDefaults on iOS and SharedPreferences on Android, it offers a simple, asynchronous key-value store for primitive types.
Adding the Package
The shared_preferences package is not bundled with Flutter — you must add it to your project. Run the following command in your terminal from the project root:
flutter pub add shared_preferences
This updates pubspec.yaml and downloads the package. Then import it wherever you need it:
import 'package:shared_preferences/shared_preferences.dart';
Getting an Instance
You obtain a SharedPreferences instance by calling the static async factory SharedPreferences.getInstance(). Because it returns a Future, you must await it inside an async function. A common pattern is to load preferences inside initState of a StatefulWidget:
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool _darkMode = false;
String _username = '';
@override
void initState() {
super.initState();
_loadPreferences();
}
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_darkMode = prefs.getBool('dark_mode') ?? false;
_username = prefs.getString('username') ?? '';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Column(
children: [
Text('Hello, $_username'),
Switch(
value: _darkMode,
onChanged: (value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('dark_mode', value);
setState(() => _darkMode = value);
},
),
],
),
);
}
}
SharedPreferences.getInstance() is inexpensive after the first call because the platform caches the instance. However, it is still asynchronous, so always await it. Never block the UI thread by calling it synchronously.Reading and Writing Primitive Values
SharedPreferences supports five primitive types. Each type has a dedicated getter and setter pair:
- String —
getString(key)/setString(key, value) - int —
getInt(key)/setInt(key, value) - double —
getDouble(key)/setDouble(key, value) - bool —
getBool(key)/getBool(key, value)/setBool(key, value) - List<String> —
getStringList(key)/setStringList(key, value)
All getters return null if the key does not exist, so always apply the null-coalescing operator (??) to supply a default value. All setters return Future<bool> indicating success — await them to be safe:
Future<void> saveUserData() async {
final prefs = await SharedPreferences.getInstance();
// Writing
await prefs.setString('username', 'Edrees');
await prefs.setInt('launch_count', 42);
await prefs.setDouble('font_size', 16.5);
await prefs.setBool('notifications_enabled', true);
await prefs.setStringList('recent_searches', ['flutter', 'dart', 'firebase']);
// Reading with null-safe defaults
final username = prefs.getString('username') ?? 'Guest';
final launches = prefs.getInt('launch_count') ?? 0;
final fontSize = prefs.getDouble('font_size') ?? 14.0;
final notifs = prefs.getBool('notifications_enabled') ?? true;
final searches = prefs.getStringList('recent_searches') ?? [];
print('$username has opened the app $launches times');
}
Removing Keys and Clearing All Data
You can remove a specific key or wipe the entire preferences store. Use remove(key) to delete one entry and clear() to erase everything — typically on logout:
Future<void> handleLogout() async {
final prefs = await SharedPreferences.getInstance();
// Remove a single key
await prefs.remove('auth_token');
// Or clear everything (use with caution)
await prefs.clear();
}
// Check whether a key exists before reading
Future<void> checkOnboarding() async {
final prefs = await SharedPreferences.getInstance();
final hasSeenOnboarding = prefs.containsKey('onboarding_complete');
if (!hasSeenOnboarding) {
// Show onboarding flow
}
}
Null-Safety Best Practices
Because every getter can return null, always guard reads with a default. For complex defaults or when the same preference is read in multiple places, encapsulate access in a dedicated service class rather than scattering ?? operators across your UI code:
class PreferencesService {
static const _kDarkMode = 'dark_mode';
static const _kUsername = 'username';
static const _kLaunchCount = 'launch_count';
Future<SharedPreferences> get _prefs => SharedPreferences.getInstance();
Future<bool> getDarkMode() async =>
(await _prefs).getBool(_kDarkMode) ?? false;
Future<void> setDarkMode(bool value) async =>
(await _prefs).setBool(_kDarkMode, value);
Future<String> getUsername() async =>
(await _prefs).getString(_kUsername) ?? 'Guest';
Future<void> setUsername(String name) async =>
(await _prefs).setString(_kUsername, name);
Future<int> incrementLaunchCount() async {
final p = await _prefs;
final count = (p.getInt(_kLaunchCount) ?? 0) + 1;
await p.setInt(_kLaunchCount, count);
return count;
}
}
static const strings in one place. This prevents typo-induced bugs where you write with one key name and read with a slightly different one, silently getting null back every time.flutter_secure_storage package for secrets that require encryption at rest.Summary
SharedPreferences is the go-to solution for persisting small, non-sensitive user preferences and app state across sessions. Key points to remember:
- Add the package with
flutter pub add shared_preferencesand import it. - Obtain the instance with
await SharedPreferences.getInstance(). - Use type-specific getters/setters for String, int, double, bool, and
List<String>. - All getters return
nullwhen a key is absent — always supply a??default. - Encapsulate preferences access in a service class for cleaner, null-safe code.
- Never store secrets; use
flutter_secure_storagefor sensitive data.