local_first_shared_preferences
A lightweight configuration storage adapter for the local_first ecosystem. Built on Flutter's SharedPreferences for storing app settings, feature flags, and metadata with namespace support.
Note: This package provides config storage only (implements
ConfigKeyValueStorage). For event/data storage, use local_first_hive_storage or local_first_sqlite_storage.
Why local_first_shared_preferences?
- Platform Native: Uses platform-specific preferences stores (NSUserDefaults on iOS, SharedPreferences on Android)
- Lightweight: Perfect for storing small configuration values and app settings
- Simple API: Key-value storage with type safety
- Namespace Support: Isolate settings per user or tenant
- Zero Dependencies: Only requires Flutter's built-in
shared_preferencesplugin - Instant Access: No async initialization required for reads after first load
Features
- ✅ Config Storage: Store app settings, feature flags, and metadata
- ✅ Type Safe: Generic get/set methods with type checking
- ✅ Namespace Support: Prefix keys per user/tenant automatically
- ✅ Platform Native: Uses native preference stores on each platform
- ✅ Simple API: Easy to use key-value interface
- ✅ Null Safety: Full null safety support
Installation
Add the dependency to your pubspec.yaml:
dependencies:
local_first: ^0.6.0
local_first_shared_preferences: ^0.2.0
Then install it with:
flutter pub get
Quick Start
import 'package:local_first_shared_preferences/local_first_shared_preferences.dart';
// 1) Create storage instance
final storage = SharedPreferencesConfigStorage(
namespace: 'user_alice', // Optional: isolate data per user
);
// 2) Initialize
await storage.initialize();
// 3) Store values
await storage.setConfigValue('theme', 'dark');
await storage.setConfigValue('notifications_enabled', true);
await storage.setConfigValue('sync_interval', 30);
// 4) Retrieve values
final theme = await storage.getConfigValue<String>('theme');
final notificationsEnabled = await storage.getConfigValue<bool>('notifications_enabled');
final syncInterval = await storage.getConfigValue<int>('sync_interval');
print('Theme: $theme'); // dark
print('Notifications: $notificationsEnabled'); // true
print('Sync interval: $syncInterval seconds'); // 30
Integration with LocalFirst
Use SharedPreferences for config storage alongside event storage:
import 'package:local_first/local_first.dart';
import 'package:local_first_hive_storage/local_first_hive_storage.dart';
import 'package:local_first_shared_preferences/local_first_shared_preferences.dart';
Future<void> main() async {
// SharedPreferences for config (settings, sync state)
final configStorage = SharedPreferencesConfigStorage();
await configStorage.initialize();
// Hive for event storage (actual data)
final client = LocalFirstClient(
repositories: [todoRepository, userRepository],
localStorage: HiveLocalFirstStorage(),
configStorage: configStorage, // Use SharedPreferences for config
syncStrategies: [
// Strategies can use configStorage for sync state
],
);
await client.initialize();
// Store sync state
await client.setConfigValue('last_sync_seq', 42);
// Store app preferences
await client.setConfigValue('dark_mode', true);
// Retrieve values
final lastSeq = await client.getConfigValue<int>('last_sync_seq');
final darkMode = await client.getConfigValue<bool>('dark_mode');
}
Supported Types
SharedPreferences supports these types:
| Type | Example | Use Case |
|---|---|---|
bool |
true |
Feature flags, toggle settings |
int |
42 |
Counters, sync sequences, numeric settings |
double |
3.14 |
Ratings, decimal settings |
String |
'hello' |
User names, tokens, text settings |
List<String> |
['a', 'b'] |
Tags, categories, string lists |
Example: Storing Different Types
final storage = SharedPreferencesConfigStorage();
await storage.initialize();
// Boolean
await storage.setConfigValue('dark_mode', true);
await storage.setConfigValue('notifications_enabled', false);
// Integer
await storage.setConfigValue('sync_interval_seconds', 30);
await storage.setConfigValue('max_retries', 3);
// Double
await storage.setConfigValue('font_scale', 1.2);
await storage.setConfigValue('volume_level', 0.75);
// String
await storage.setConfigValue('theme', 'dark');
await storage.setConfigValue('language', 'en_US');
await storage.setConfigValue('auth_token', 'eyJhbGciOi...');
// List<String>
await storage.setConfigValue('enabled_features', ['chat', 'notifications', 'analytics']);
await storage.setConfigValue('favorite_categories', ['work', 'personal']);
// Retrieve with type safety
final darkMode = await storage.getConfigValue<bool>('dark_mode');
final syncInterval = await storage.getConfigValue<int>('sync_interval_seconds');
final fontScale = await storage.getConfigValue<double>('font_scale');
final theme = await storage.getConfigValue<String>('theme');
final features = await storage.getConfigValue<List<String>>('enabled_features');
Namespace Support
Isolate configuration per user or tenant:
final storage = SharedPreferencesConfigStorage();
await storage.initialize();
// Switch to Alice's namespace
await storage.useNamespace('user_alice');
await storage.setConfigValue('theme', 'dark');
await storage.setConfigValue('notifications', true);
// Switch to Bob's namespace
await storage.useNamespace('user_bob');
await storage.setConfigValue('theme', 'light');
await storage.setConfigValue('notifications', false);
// Back to Alice
await storage.useNamespace('user_alice');
final aliceTheme = await storage.getConfigValue<String>('theme'); // 'dark'
// Back to Bob
await storage.useNamespace('user_bob');
final bobTheme = await storage.getConfigValue<String>('theme'); // 'light'
How Namespaces Work
Namespaces automatically prefix keys:
// Without namespace
await storage.setConfigValue('theme', 'dark');
// Stored as: "theme" = "dark"
// With namespace "user_alice"
await storage.useNamespace('user_alice');
await storage.setConfigValue('theme', 'dark');
// Stored as: "user_alice__theme" = "dark"
// With namespace "user_bob"
await storage.useNamespace('user_bob');
await storage.setConfigValue('theme', 'light');
// Stored as: "user_bob__theme" = "light"
Use cases:
- 👤 Multi-user apps (different settings per user)
- 🏢 Multi-tenant apps (isolate tenant config)
- 📱 Multiple account support
- 🔐 Testing (isolate test data from production)
Common Use Cases
1. Feature Flags
// Store feature flags
await storage.setConfigValue('feature_chat_enabled', true);
await storage.setConfigValue('feature_analytics_enabled', false);
await storage.setConfigValue('feature_dark_mode_enabled', true);
// Check if feature is enabled
final chatEnabled = await storage.getConfigValue<bool>('feature_chat_enabled');
if (chatEnabled == true) {
// Show chat UI
}
2. User Preferences
// Theme preference
await storage.setConfigValue('theme_mode', 'dark'); // 'light', 'dark', 'system'
// Notification settings
await storage.setConfigValue('notifications_enabled', true);
await storage.setConfigValue('notification_sound', 'chime');
// Accessibility
await storage.setConfigValue('font_size', 'large'); // 'small', 'medium', 'large'
await storage.setConfigValue('reduce_animations', false);
// Language
await storage.setConfigValue('locale', 'en_US');
3. Sync State Management
// Store last sync sequence per repository
await storage.setConfigValue('__last_seq__todos', 42);
await storage.setConfigValue('__last_seq__users', 15);
// Store last sync timestamp
await storage.setConfigValue('last_sync_time', DateTime.now().toIso8601String());
// Retrieve for next sync
final lastTodoSeq = await storage.getConfigValue<int>('__last_seq__todos');
if (lastTodoSeq != null) {
// Sync only events after sequence 42
}
4. App State Persistence
// Remember user selections
await storage.setConfigValue('selected_workspace_id', 'workspace_123');
await storage.setConfigValue('last_viewed_project', 'project_456');
// Onboarding state
await storage.setConfigValue('onboarding_completed', true);
await storage.setConfigValue('tutorial_step', 3);
// Cache control
await storage.setConfigValue('cache_version', 2);
5. Authentication State
// Store auth tokens (consider secure storage for production)
await storage.setConfigValue('auth_token', 'eyJhbGc...');
await storage.setConfigValue('refresh_token', 'dGhlIHN...');
await storage.setConfigValue('token_expires_at', '2026-02-01T10:00:00Z');
// User info
await storage.setConfigValue('logged_in_user_id', 'user_123');
await storage.setConfigValue('user_email', 'alice@example.com');
Best Practices
1. Use Type-Safe Retrieval
// Good: Specify type and handle null
final theme = await storage.getConfigValue<String>('theme') ?? 'light';
// Also good: Check for null explicitly
final syncInterval = await storage.getConfigValue<int>('sync_interval');
if (syncInterval != null) {
// Use syncInterval
} else {
// Use default
const defaultInterval = 30;
}
2. Use Consistent Key Naming
// Good: Consistent naming convention
await storage.setConfigValue('feature_chat_enabled', true);
await storage.setConfigValue('feature_analytics_enabled', false);
await storage.setConfigValue('user_theme_preference', 'dark');
await storage.setConfigValue('user_language', 'en_US');
// Less clear: Inconsistent naming
await storage.setConfigValue('ChatFeature', true); // Mixed case
await storage.setConfigValue('enable-analytics', false); // Dashes vs underscores
await storage.setConfigValue('THEME', 'dark'); // All caps
3. Store Small Values Only
SharedPreferences is designed for small values:
// Good: Small config values
await storage.setConfigValue('theme', 'dark');
await storage.setConfigValue('user_id', 'user_123');
await storage.setConfigValue('sync_interval', 30);
// Bad: Large data (use Hive/SQLite instead)
await storage.setConfigValue('cached_todos', jsonEncode(allTodos)); // ❌
await storage.setConfigValue('large_image_data', base64Image); // ❌
Rule of thumb: Use SharedPreferences for config (<100 KB). Use Hive/SQLite for data.
4. Use Namespaces for Multi-User Apps
class AuthService {
final SharedPreferencesConfigStorage storage;
Future<void> login(String userId) async {
await storage.useNamespace('user_$userId');
// Now all config is isolated to this user
}
Future<void> logout() async {
await storage.useNamespace(null); // Clear namespace
}
}
5. Provide Defaults for Missing Values
Future<String> getTheme() async {
return await storage.getConfigValue<String>('theme') ?? 'light';
}
Future<int> getSyncInterval() async {
return await storage.getConfigValue<int>('sync_interval') ?? 30;
}
Future<bool> areNotificationsEnabled() async {
return await storage.getConfigValue<bool>('notifications_enabled') ?? true;
}
Comparison with Hive/SQLite
| Feature | SharedPreferences | Hive | SQLite |
|---|---|---|---|
| Purpose | Config/settings | Data storage | Data storage |
| Data Size | Small (<100 KB) | Medium-Large | Large |
| Performance | Very fast (cached) | Very fast | Fast |
| Query Support | Key-value only | Basic filtering | Rich SQL queries |
| Storage Type | Platform native | File-based | Database |
| Best For | Settings, flags | Events, documents | Structured data |
| Namespace Support | Yes (manual prefix) | Yes (built-in) | Yes (built-in) |
Use SharedPreferences for:
- ✅ User preferences (theme, language, font size)
- ✅ Feature flags (boolean toggles)
- ✅ Small config values (API endpoints, timeout values)
- ✅ Sync metadata (last sequence, last sync time)
- ✅ Authentication state (user ID, simple tokens)
Use Hive/SQLite for:
- ✅ Application data (todos, notes, messages)
- ✅ Event sourcing (LocalFirst events)
- ✅ Large datasets
- ✅ Data requiring queries/filtering
Troubleshooting
Values Not Persisting
Symptoms: Config values don't persist after app restart
Solutions:
- Ensure you await
initialize():await storage.initialize(); // Don't forget await! - Ensure you await
setConfigValue():await storage.setConfigValue('theme', 'dark'); // Don't forget await! - Check platform permissions (iOS:
NSUserDefaults, Android:SharedPreferences)
Namespace Issues
Symptoms: Values from different namespaces mixing
Solutions:
- Always call
useNamespace()before operations:await storage.useNamespace('user_$userId'); final theme = await storage.getConfigValue<String>('theme'); - Verify namespace is set correctly:
print('Current namespace: ${storage.currentNamespace}');
Type Mismatch Errors
Symptoms: Getting null or wrong type
Solutions:
- Ensure you use the correct type:
// Stored as int await storage.setConfigValue('count', 42); // Retrieve as int, not String final count = await storage.getConfigValue<int>('count'); // ✅ final wrongType = await storage.getConfigValue<String>('count'); // ❌ null
Platform-Specific Issues
iOS:
- NSUserDefaults has a limit (~4 MB typically)
- Values are stored in
~/Library/Preferences/[bundle-id].plist
Android:
- SharedPreferences stores XML files
- Located in
/data/data/[package]/shared_prefs/
Web:
- Uses localStorage (limited to ~5-10 MB depending on browser)
- Cleared when user clears browser data
Solution: Keep values small and use Hive/SQLite for larger data.
Testing
Unit Tests
import 'package:flutter_test/flutter_test.dart';
import 'package:local_first_shared_preferences/local_first_shared_preferences.dart';
void main() {
late SharedPreferencesConfigStorage storage;
setUp(() async {
storage = SharedPreferencesConfigStorage();
await storage.initialize();
});
test('should store and retrieve string value', () async {
await storage.setConfigValue('key', 'value');
final result = await storage.getConfigValue<String>('key');
expect(result, equals('value'));
});
test('should return null for non-existent key', () async {
final result = await storage.getConfigValue<String>('non_existent');
expect(result, isNull);
});
test('should isolate values by namespace', () async {
await storage.useNamespace('ns1');
await storage.setConfigValue('key', 'value1');
await storage.useNamespace('ns2');
await storage.setConfigValue('key', 'value2');
await storage.useNamespace('ns1');
final result1 = await storage.getConfigValue<String>('key');
expect(result1, equals('value1'));
await storage.useNamespace('ns2');
final result2 = await storage.getConfigValue<String>('key');
expect(result2, equals('value2'));
});
}
Example App
This package includes a complete example demonstrating:
- SharedPreferences for config storage
- Hive for event storage
- Namespace isolation
- Multi-user support
To run the example:
cd local_first_shared_preferences/example
flutter pub get
flutter run
Platform Support
| Platform | Support | Storage Location |
|---|---|---|
| Android | ✅ | SharedPreferences XML |
| iOS | ✅ | NSUserDefaults plist |
| macOS | ✅ | NSUserDefaults plist |
| Linux | ✅ | JSON file in XDG directory |
| Windows | ✅ | Registry or JSON file |
| Web | ✅ | localStorage |
Contributing
Contributions are welcome. See CONTRIBUTING.md for guidelines.
Support the Project 💰
Your contributions help us enhance and maintain our plugins. Donations are used to procure devices and equipment for testing compatibility across platforms and versions.
License
This project is available under the MIT License. See LICENSE for details.

