Appearance
💾 Local Storage Standards
Table of Contents
Core Standards
Storage Setup Rules
- Always use centralized
LocalStorageclass for all local storage operations - Use dependency injection with
getItfor access - Choose appropriate storage type based on data sensitivity
- Use consistent key naming conventions
- Implement proper error handling for storage operations
- Provide getter, setter, and clear methods for each stored value
Storage Type Selection Rules
- Use
SharedPreferencesfor non-sensitive data (settings, preferences, cache) - Use
FlutterSecureStoragefor sensitive data (tokens, passwords, personal info) - Consider data persistence requirements
- Evaluate performance needs for frequent access
- Check platform compatibility for secure storage
dart
// Non-sensitive data - SharedPreferences
static const String _themeKey = 'app_theme';
static const String _languageKey = 'app_language';
static const String _lastSyncKey = 'last_sync_time';
// Sensitive data - FlutterSecureStorage
static const String _authTokenKey = 'auth_token';
static const String _userPasswordKey = 'user_password';
static const String _biometricKey = 'biometric_enabled';Storage Types
SharedPreferences Usage
- Use for app settings and user preferences
- Use for cached data that's not sensitive
- Use for UI state persistence
- Use for analytics data and app metrics
- Handle null values properly
dart
// String values
Future<bool> storeTheme(String theme) async =>
_sharedPreferences.setString(_themeKey, theme);
String? get theme => _sharedPreferences.getString(_themeKey);
Future<bool> clearTheme() async => _sharedPreferences.remove(_themeKey);
// Boolean values
Future<bool> storeNotificationsEnabled(bool enabled) async =>
_sharedPreferences.setBool('notifications_enabled', enabled);
bool get notificationsEnabled =>
_sharedPreferences.getBool('notifications_enabled') ?? true;
// Integer values
Future<bool> storeUserId(int userId) async =>
_sharedPreferences.setInt('user_id', userId);
int? get userId => _sharedPreferences.getInt('user_id');
// List values
Future<bool> storeRecentSearches(List<String> searches) async =>
_sharedPreferences.setStringList('recent_searches', searches);
List<String> get recentSearches =>
_sharedPreferences.getStringList('recent_searches') ?? [];FlutterSecureStorage Usage
- Use for authentication tokens and credentials
- Use for personal information and sensitive data
- Handle secure storage errors gracefully
dart
// Authentication tokens
Future<void> storeAuthToken(String token) async =>
await _secureStorage.write(key: _authTokenKey, value: token);
Future<String?> get authToken async =>
await _secureStorage.read(key: _authTokenKey);
Future<void> clearAuthToken() async =>
await _secureStorage.delete(key: _authTokenKey);
// User credentials
Future<void> storeUserCredentials(String username, String password) async {
await _secureStorage.write(key: 'username', value: username);
await _secureStorage.write(key: 'password', value: password);
}
Future<Map<String, String?>> get userCredentials async => {
'username': await _secureStorage.read(key: 'username'),
'password': await _secureStorage.read(key: 'password'),
};
// Clear all secure storage
Future<void> clearAllSecureData() async =>
await _secureStorage.deleteAll();Implementation Guidelines
Key Naming Conventions
- Use descriptive key names that indicate purpose
- Use snake_case for key naming
- Use consistent prefixes for related keys
- Avoid generic names like 'data' or 'value'
- Include data type in key name when helpful
dart
// ✅ Good: Descriptive and consistent naming
static const String _userThemeKey = 'user_theme';
static const String _lastLoginTimeKey = 'last_login_time';
static const String _notificationSettingsKey = 'notification_settings';
static const String _authRefreshTokenKey = 'auth_refresh_token';
// ❌ Bad: Generic and unclear naming
static const String _dataKey = 'data';
static const String _valueKey = 'value';
static const String _key1 = 'key1';
static const String _tempKey = 'temp';Method Implementation Rules
- Always provide getter, setter, and clear methods for each stored value
- Use appropriate return types for getters
- Handle null values with default values or nullable types
- Use async/await for all storage operations
- Implement proper error handling
dart
class LocalStorage {
// Theme management
static const String _themeKey = 'app_theme';
Future<bool> storeTheme(String theme) async {
try {
return await _sharedPreferences.setString(_themeKey, theme);
} catch (e) {
// Log error and return false
return false;
}
}
String get theme => _sharedPreferences.getString(_themeKey) ?? 'light';
Future<bool> clearTheme() async {
try {
return await _sharedPreferences.remove(_themeKey);
} catch (e) {
// Log error and return false
return false;
}
}
// User preferences
static const String _notificationsEnabledKey = 'notifications_enabled';
Future<bool> storeNotificationsEnabled(bool enabled) async {
try {
return await _sharedPreferences.setBool(_notificationsEnabledKey, enabled);
} catch (e) {
return false;
}
}
bool get notificationsEnabled =>
_sharedPreferences.getBool(_notificationsEnabledKey) ?? true;
Future<bool> clearNotificationsEnabled() async {
try {
return await _sharedPreferences.remove(_notificationsEnabledKey);
} catch (e) {
return false;
}
}
}Data Type Handling Rules
- Use appropriate data types for different values
- Convert complex objects to JSON strings
- Handle type conversion errors gracefully
- Use nullable types when values might not exist
- Provide default values for required settings
dart
// Complex object storage
Future<bool> storeUserSettings(UserSettings settings) async {
try {
final jsonString = jsonEncode(settings.toJson());
return await _sharedPreferences.setString('user_settings', jsonString);
} catch (e) {
return false;
}
}
UserSettings? get userSettings {
try {
final jsonString = _sharedPreferences.getString('user_settings');
if (jsonString != null) {
final json = jsonDecode(jsonString) as Map<String, dynamic>;
return UserSettings.fromJson(json);
}
return null;
} catch (e) {
return null;
}
}
// Date storage
Future<bool> storeLastSyncTime(DateTime dateTime) async {
try {
return await _sharedPreferences.setString(
'last_sync_time',
dateTime.toIso8601String(),
);
} catch (e) {
return false;
}
}
DateTime? get lastSyncTime {
try {
final dateString = _sharedPreferences.getString('last_sync_time');
if (dateString != null) {
return DateTime.parse(dateString);
}
return null;
} catch (e) {
return null;
}
}Key Management
Key Organization Rules
- Group related keys together in the class
- Use static const for all key definitions
- Document key purposes with comments
- Avoid key conflicts with consistent naming
- Use versioning for key changes when needed
dart
class LocalStorage {
// User-related keys
static const String _userIdKey = 'user_id';
static const String _userEmailKey = 'user_email';
static const String _userPreferencesKey = 'user_preferences';
// App settings keys
static const String _themeKey = 'app_theme';
static const String _languageKey = 'app_language';
static const String _firstLaunchKey = 'first_launch';
// Authentication keys (secure storage)
static const String _authTokenKey = 'auth_token';
static const String _refreshTokenKey = 'refresh_token';
static const String _biometricEnabledKey = 'biometric_enabled';
// Cache keys
static const String _lastCacheUpdateKey = 'last_cache_update';
static const String _cachedDataVersionKey = 'cached_data_version';
}Access Pattern Rules
- Use dependency injection for LocalStorage access
- Access through getIt from anywhere in the app
- Handle storage errors at the access point
- Use consistent access patterns across the app
- Provide fallback values when storage fails
dart
// Access pattern example
class UserService {
final LocalStorage _localStorage = GetIt.instance<LocalStorage>();
Future<void> saveUserPreferences(UserPreferences preferences) async {
final success = await _localStorage.storeUserPreferences(preferences);
if (!success) {
// Handle storage failure
throw Exception('Failed to save user preferences');
}
}
UserPreferences getUserPreferences() {
return _localStorage.userPreferences ?? UserPreferences.defaults();
}
Future<void> clearUserData() async {
await _localStorage.clearUserPreferences();
await _localStorage.clearUserId();
}
}Common Violations
❌ DO NOT Violate These Rules
- Don't store sensitive data in SharedPreferences
- Don't use hardcoded keys without constants
- Don't ignore storage errors - always handle them
- Don't use generic key names like 'data' or 'value'
- Don't store large objects directly in SharedPreferences
- Don't forget to clear sensitive data on logout
- Don't use inconsistent naming conventions for keys
- Don't store passwords in plain text
- Don't ignore null safety when retrieving values
- Don't forget to register LocalStorage with dependency injection
✅ ALWAYS Follow These Rules
- Use appropriate storage type based on data sensitivity
- Use descriptive key names with consistent naming
- Provide getter, setter, and clear methods for each value
- Handle storage errors gracefully with try-catch
- Use dependency injection for LocalStorage access
- Convert complex objects to JSON before storage
- Provide default values for required settings
- Clear sensitive data when user logs out
- Use static const for all key definitions
- Test storage operations in different scenarios