Flutter & Shared Preferences: Storing User Settings
In modern applications, personalizing the user experience is paramount. Users often expect their preferences, such as theme settings, notification choices, or last-used configurations, to persist across app sessions. For Flutter developers, shared_preferences is a popular and straightforward solution for storing such lightweight user settings.
Understanding Shared Preferences
shared_preferences is a Flutter plugin that wraps platform-specific persistent storage mechanisms. On Android, it uses SharedPreferences, and on iOS, it uses NSUserDefaults. It provides a simple API for reading and writing primitive data types (booleans, integers, doubles, strings, and lists of strings) using a key-value pair system. It's ideal for non-sensitive data and user preferences, but not suitable for large or complex datasets, or for sensitive information like passwords.
Getting Started: Adding the Dependency
To use shared_preferences in your Flutter project, you first need to add it as a dependency in your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.0 # Use the latest version available
After adding the dependency, run flutter pub get to fetch the package.
Basic Usage: Saving and Retrieving Data
The core of shared_preferences involves asynchronously getting an instance of SharedPreferences and then using its setter and getter methods.
1. Saving Data
To save a value, you first need to get an instance of SharedPreferences and then call the appropriate setter method based on the data type (e.g., setBool, setInt, setString, setDouble, setStringList).
import 'package:shared_preferences/shared_preferences.dart';
Future saveUserSettings({
required bool isDarkMode,
required int fontSize,
required String username,
}) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDarkMode', isDarkMode);
await prefs.setInt('fontSize', fontSize);
await prefs.setString('username', username);
print('User settings saved successfully!');
}
2. Retrieving Data
Retrieving data is equally straightforward, using the corresponding getter methods (e.g., getBool, getInt, getString). These methods return null if the key does not exist, so it's good practice to provide a default fallback value using the ?? operator.
import 'package:shared_preferences/shared_preferences.dart';
Future loadUserSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final bool? isDarkMode = prefs.getBool('isDarkMode');
final int? fontSize = prefs.getInt('fontSize');
final String? username = prefs.getString('username');
print('Loaded Settings:');
print('Dark Mode: ${isDarkMode ?? false}'); // Default to false
print('Font Size: ${fontSize ?? 14}'); // Default to 14
print('Username: ${username ?? 'Guest'}'); // Default to 'Guest'
}
3. Removing Data
If you need to remove a specific key-value pair, use the remove() method:
import 'package:shared_preferences/shared_preferences.dart';
Future removeSetting(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove(key);
print('Setting "$key" removed.');
}
4. Clearing All Data
To clear all stored preferences for your application, use the clear() method:
import 'package:shared_preferences/shared_preferences.dart';
Future clearAllSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.clear();
print('All settings cleared.');
}
Example: Implementing a Theme Switcher
Let's integrate this into a simple Flutter UI to persist a user's dark mode preference.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
bool _isDarkMode = false;
@override
void initState() {
super.initState();
_loadThemeSetting();
}
Future _loadThemeSetting() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_isDarkMode = prefs.getBool('isDarkMode') ?? false;
});
}
Future _toggleTheme(bool value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_isDarkMode = value;
});
await prefs.setBool('isDarkMode', value);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter User Settings',
theme: _isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: const Text('User Settings'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Dark Mode',
style: Theme.of(context).textTheme.headlineSmall,
),
Switch(
value: _isDarkMode,
onChanged: _toggleTheme,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Example of saving other settings
saveUserSettings(
isDarkMode: _isDarkMode,
fontSize: 18,
username: 'FlutterDev',
);
},
child: const Text('Save Other Settings (Demo)'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: loadUserSettings,
child: const Text('Load Other Settings (Demo)'),
),
],
),
),
),
);
}
}
// Re-using the functions defined earlier for demonstration
Future saveUserSettings({
required bool isDarkMode,
required int fontSize,
required String username,
}) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDarkMode', isDarkMode); // This is also handled by _toggleTheme
await prefs.setInt('fontSize', fontSize);
await prefs.setString('username', username);
print('Demo: Other user settings saved successfully!');
}
Future loadUserSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final bool? isDarkMode = prefs.getBool('isDarkMode');
final int? fontSize = prefs.getInt('fontSize');
final String? username = prefs.getString('username');
print('Demo: Loaded Other Settings:');
print('Dark Mode: ${isDarkMode ?? false}');
print('Font Size: ${fontSize ?? 14}');
print('Username: ${username ?? 'Guest'}');
}
Best Practices and Considerations
- Asynchronous Operations: All `shared_preferences` operations are asynchronous and return `Future`s. Always `await` them to ensure data is written or read correctly before proceeding.
- Data Types: Only primitive types (bool, int, double, String) and `List
` are directly supported. For complex objects, you'll need to serialize them to JSON strings before saving and deserialize them upon retrieval. - Not for Sensitive Data: `shared_preferences` stores data in plain text on the device. It is not encrypted and can be accessed by a rooted/jailbroken device. For sensitive information, consider using plugins like `flutter_secure_storage`.
- Performance: While efficient for small amounts of data, `shared_preferences` is not optimized for large-scale data storage or frequent, high-volume operations. For databases, consider SQLite (e.g., `sqflite`) or NoSQL solutions.
- Error Handling: While `shared_preferences` is generally robust, it's good practice to wrap critical operations in `try-catch` blocks, especially when dealing with external dependencies or user input.
Conclusion
shared_preferences provides a simple, efficient, and widely adopted solution for persisting lightweight user settings and preferences in Flutter applications. By understanding its capabilities and limitations, developers can significantly enhance the user experience by making their apps more personalized and convenient.