The basic persistence is to save small pieces of data so they persist when the app is closed.
Imagine your user enables “Dark Mode”. They close the App, open it tomorrow, and… Bam! It’s back to blinding white. That’s a bad experience.
To save small and simple data (preferences or settings) we don’t need a complex database. We can use a native key-value pair system using the shared_preferences package.
What it IS and what it IS NOT
- IT IS: A place to save “loose things”: a
boolean(seen/unseen), anint(high score), astring(username). - IT IS NOT: A database. Don’t try to save a list of 5,000 products there. It’s slow for large volumes and doesn’t allow complex searches.
- IT IS NOT: A secure store. Do not save passwords, tokens, or other secrets without using a secure storage solution specific to each platform.
Installation
Add it to your pubspec.yaml:
flutter pub add shared_preferences
Saving Data
Using shared_preferences is asynchronous (it takes a few milliseconds because it writes to the device’s storage).
import 'package:shared_preferences/shared_preferences.dart';
Future<void> savePreferences() async {
final prefs = SharedPreferencesAsync();
// We save values using a key
await prefs.setBool('dark_mode', true);
await prefs.setString('username', 'LuisLlamas');
await prefs.setInt('max_score', 1500);
print("Data saved");
}
The keys ('dark_mode', 'username') are case-sensitive. A good practice is to create a class with static constants (static const String keyUser = 'user') to avoid typos when writing the string.
Retrieving Data
Reading is just as easy. The only difference is that if the key doesn’t exist (it’s the first time you open the App), it will return null.
Future<void> loadPreferences() async {
final prefs = SharedPreferencesAsync();
// We read. The second parameter '?? false' is important:
// "If the key doesn't exist, return false by default".
bool isDark = await prefs.getBool('dark_mode') ?? false;
String name = await prefs.getString('username') ?? 'Guest';
print("Hello $name, dark mode: $isDark");
}
Deleting Data
If the user resets an option, you can delete that preference or clear all keys for the application.
final prefs = SharedPreferencesAsync();
// Delete a specific key
await prefs.remove('username');
// Delete EVERYTHING (Reset the App)
await prefs.clear();
Where to Place Preference Access
Do not create or query the preferences store inside build. It is not the place to start asynchronous work and you may end up with repeated reads.
The correct approach is to use the Service pattern (which we saw in Module 6) or inject it into your Provider (Module 7).
The original SharedPreferences class is a legacy API that the package plans to deprecate in the future. For new code, SharedPreferencesAsync or SharedPreferencesWithCache is recommended. The former always queries the platform storage; the latter allows synchronous reads from a local cache.
Example with a Theme Provider
Let’s make the dark theme persist.
class ThemeProvider extends ChangeNotifier {
bool _isDark = false;
final SharedPreferencesAsync _prefs = SharedPreferencesAsync();
bool get isDark => _isDark;
// Constructor: Load the preference on startup
ThemeProvider() {
_loadTheme();
}
Future<void> _loadTheme() async {
_isDark = await _prefs.getBool('isDark') ?? false;
notifyListeners(); // Notify the UI to update
}
Future<void> toggleTheme() async {
_isDark = !_isDark;
notifyListeners();
// Save the change
await _prefs.setBool('isDark', _isDark);
}
}