bosc_flutter_loacl_storage_package (1.0.0)
Installation
dart pub add bosc_flutter_loacl_storage_package:1.0.0 --hosted-url=About this package
BOSC Flutter Local Storage Package
A robust, production-ready Local Storage Client for Flutter using GetStorage. This package provides a clean, type-safe API for local data persistence with comprehensive error handling, serialization, and optional encryption support.
Features
- ✅ Type-Safe Operations - Generic read/write methods with full type safety
- ✅ Clean Architecture - Separation of concerns with clear component boundaries
- ✅ Custom Object Serialization - Register serializers for complex objects
- ✅ Optional Encryption - Built-in support for encrypting sensitive data
- ✅ Comprehensive Error Handling - Custom exceptions for granular error management
- ✅ Batch Operations - Efficient multi-key read/write operations
- ✅ Reactive Updates - Stream-based change notifications
- ✅ Backup & Restore - Easy data backup and restoration
- ✅ Testing Support - Isolated test configurations
- ✅ Well Documented - Extensive documentation and examples
- ✅ Production Ready - Battle-tested error handling and edge cases
Installation
Add this to your package's pubspec.yaml file:
dependencies:
bosc_flutter_loacl_storage_package: ^0.0.1
Then run:
flutter pub get
Quick Start
1. Initialize in main()
import 'package:bosc_flutter_loacl_storage_package/bosc_flutter_loacl_storage_package.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize storage
await LocalStorageClient.instance.initialize();
runApp(MyApp());
}
2. Basic Usage
final storage = LocalStorageClient.instance;
// Write data
await storage.write<String>(StorageKeys.authToken, 'abc123');
await storage.write<int>(StorageKeys.userId, 12345);
await storage.write<bool>(StorageKeys.isDarkMode, true);
// Read data
final token = storage.read<String>(StorageKeys.authToken);
final userId = storage.read<int>(StorageKeys.userId, defaultValue: 0);
// Check existence
if (storage.containsKey(StorageKeys.authToken)) {
print('User is authenticated');
}
// Remove data
await storage.remove(StorageKeys.authToken);
// Clear all
await storage.clear();
Architecture
The package follows clean architecture principles with clear separation of concerns:
lib/
├── src/
│ ├── core/
│ │ ├── storage_exceptions.dart # Custom exceptions
│ │ └── storage_keys.dart # Type-safe keys
│ ├── models/
│ │ └── storage_config.dart # Configuration
│ ├── services/
│ │ └── storage_service.dart # Core service layer
│ ├── utils/
│ │ ├── storage_serializer.dart # Serialization
│ │ └── storage_encryption.dart # Encryption hooks
│ └── storage_client.dart # Main API
└── bosc_flutter_loacl_storage_package.dart # Barrel file
Detailed Usage
Configuration
// Development configuration
final storage = LocalStorageClient(
config: StorageConfig.development(
boxName: 'dev_storage',
),
);
// Production configuration
final storage = LocalStorageClient(
config: StorageConfig.production(
boxName: 'prod_storage',
enableAutoBackup: true,
),
);
// Custom configuration
final storage = LocalStorageClient(
config: StorageConfig(
boxName: 'my_storage',
enableLogging: true,
throwOnError: true,
maxRetries: 3,
),
);
Custom Object Storage
// 1. Create your model with toJson/fromJson
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
};
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
// 2. Register the serializer
StorageSerializer.register<User>(
serializer: (user) => user.toJson(),
deserializer: (json) => User.fromJson(json),
);
// 3. Store and retrieve
final user = User(id: 1, name: 'John Doe', email: 'john@example.com');
await storage.write<User>(StorageKeys.userProfile, user);
final storedUser = storage.read<User>(StorageKeys.userProfile);
print(storedUser?.name); // John Doe
Encryption
// Implement your encryption handler
class MyEncryptionHandler implements StorageEncryptionHandler {
final String key;
MyEncryptionHandler(this.key);
@override
String encrypt(String plainText) {
// Use a real encryption library like 'encrypt' package
// This is just a placeholder
return encryptData(plainText, key);
}
@override
String decrypt(String encrypted) {
return decryptData(encrypted, key);
}
}
// Use encrypted storage
final storage = LocalStorageClient(
encryptionHandler: MyEncryptionHandler('my_secret_key'),
);
// Write encrypted data
await storage.writeEncrypted(StorageKeys.authToken, 'sensitive_token');
// Read automatically decrypts
final token = storage.read<String>(StorageKeys.authToken);
Batch Operations
// Write multiple values at once
await storage.writeBatch({
StorageKeys.userId: 123,
StorageKeys.userName: 'John Doe',
StorageKeys.userEmail: 'john@example.com',
StorageKeys.isDarkMode: true,
});
// Remove multiple keys
await storage.removeAll([
StorageKeys.authToken,
StorageKeys.refreshToken,
StorageKeys.userId,
]);
Reactive Updates
// Listen to changes on a key
storage.listen<String>(StorageKeys.authToken).listen((token) {
print('Auth token changed: $token');
if (token == null) {
// Handle logout
Navigator.pushReplacementNamed(context, '/login');
}
});
// Listen to user profile changes
storage.listen<User>(StorageKeys.userProfile).listen((user) {
if (user != null) {
print('User updated: ${user.name}');
}
});
Backup & Restore
// Create backup
final backup = storage.createBackup();
// ... perform operations ...
// Restore if needed
await storage.restoreBackup(backup);
// Export to JSON string
final jsonBackup = storage.exportToJson();
// Save to file or cloud
// Import from JSON
await storage.importFromJson(jsonBackup);
Error Handling
try {
// This will throw if key doesn't exist
final token = storage.readRequired<String>(StorageKeys.authToken);
print('Token: $token');
} on StorageReadException catch (e) {
print('Read error: ${e.message}');
print('Key: ${e.key}');
}
// With default values (won't throw)
final token = storage.read<String>(
StorageKeys.authToken,
defaultValue: 'default_token',
);
Custom Storage Keys
Define your keys in one place for type safety and consistency:
abstract class AppStorageKeys {
// Authentication
static const String authToken = 'auth_token';
static const String refreshToken = 'refresh_token';
// User Data
static const String userProfile = 'user_profile';
static const String userId = 'user_id';
// Settings
static const String isDarkMode = 'is_dark_mode';
static const String languageCode = 'language_code';
}
// Usage
await storage.write<String>(AppStorageKeys.authToken, 'token');
The package includes a comprehensive StorageKeys class with common keys. You can:
- Use the predefined keys
- Extend it with your own
- Create a completely custom keys class
Key Extensions
// Create namespaced keys for multi-tenant apps
final tenantKey = StorageKeys.authToken.withNamespace('tenant_123');
await storage.write<String>(tenantKey, 'tenant_token');
// Create versioned keys for migrations
final keyV2 = StorageKeys.userProfile.withVersion(2);
// Check if a key is sensitive
if (StorageKeys.authToken.isSensitive) {
// Use encryption
await storage.writeEncrypted(StorageKeys.authToken, token);
}
Testing
The package provides excellent testing support:
testWidgets('storage test', (tester) async {
// Create isolated test storage
final storage = LocalStorageClient.test();
await storage.initialize();
// Run your tests
await storage.write<String>('test_key', 'test_value');
expect(storage.read<String>('test_key'), 'test_value');
// Clean up
await storage.clear();
storage.dispose();
});
API Reference
Main Methods
Write Operations
write<T>(String key, T value)- Write any typewriteEncrypted<T>(String key, T value)- Write with encryptionwriteBatch(Map<String, dynamic> entries)- Write multiple values
Read Operations
read<T>(String key, {T? defaultValue})- Read with optional defaultreadRequired<T>(String key)- Read or throw if missingreadOrWrite<T>(String key, {required T defaultValue})- Read or initialize
Query Operations
containsKey(String key)- Check if key existsgetKeys()- Get all keysgetAll()- Get all key-value pairsisEmpty- Check if storage is emptylength- Get item count
Delete Operations
remove(String key)- Remove single keyremoveAll(List<String> keys)- Remove multiple keysclear()- Clear all dataclearAuthData()- Clear auth-related keys
Reactive Operations
listen<T>(String key)- Listen to key changes
Utility Operations
createBackup()- Create backuprestoreBackup(Map<String, dynamic> backup)- Restore from backupexportToJson()- Export to JSON stringimportFromJson(String json)- Import from JSON
Exception Types
StorageException- Base exceptionStorageInitializationException- Initialization failedStorageReadException- Read operation failedStorageWriteException- Write operation failedStorageDeleteException- Delete operation failedStorageClearException- Clear operation failedStorageSerializationException- Serialization/deserialization failedStorageEncryptionException- Encryption/decryption failed
Best Practices
✅ Do
-
Initialize once in main()
await LocalStorageClient.instance.initialize(); -
Use type-safe keys
await storage.write<String>(StorageKeys.authToken, token); -
Register serializers for custom objects
StorageSerializer.register<User>(...); -
Encrypt sensitive data
await storage.writeEncrypted(StorageKeys.authToken, token); -
Handle errors gracefully
try { final value = storage.readRequired<String>(key); } on StorageReadException catch (e) { // Handle error } -
Use batch operations for multiple writes
await storage.writeBatch({...});
❌ Don't
- Don't store large files - Use file system instead
- Don't store sensitive data without encryption
- Don't forget to initialize
- Don't use arbitrary strings as keys - Use constants
- Don't ignore exceptions in production
Common Mistakes
Mistake 1: Not Initializing
// ❌ Wrong
final token = LocalStorageClient.instance.read<String>('token');
// ✅ Correct
await LocalStorageClient.instance.initialize();
final token = LocalStorageClient.instance.read<String>('token');
Mistake 2: Storing Sensitive Data Without Encryption
// ❌ Wrong
await storage.write<String>(StorageKeys.authToken, token);
// ✅ Correct
await storage.writeEncrypted(StorageKeys.authToken, token);
Mistake 3: Not Using Type Parameters
// ❌ Wrong - No type safety
await storage.write('userId', 123);
var userId = storage.read('userId');
// ✅ Correct - Type safe
await storage.write<int>('userId', 123);
int? userId = storage.read<int>('userId');
Mistake 4: Not Handling Missing Keys
// ❌ Wrong - May return null unexpectedly
final userId = storage.read<int>('userId');
print(userId + 1); // Runtime error if null
// ✅ Correct - Handle null or use default
final userId = storage.read<int>('userId', defaultValue: 0);
print(userId + 1);
Performance Considerations
- Batch Operations - Use
writeBatchfor multiple writes - Lazy Initialization - Enable if you don't need storage immediately
- Avoid Frequent Clears - Clear is expensive; remove specific keys instead
- Serialization - Register serializers for complex objects once at startup
- Listeners - Dispose streams when no longer needed
Migration from Other Storage Solutions
From SharedPreferences
// Before (SharedPreferences)
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', token);
final token = prefs.getString('token');
// After (LocalStorageClient)
final storage = LocalStorageClient.instance;
await storage.initialize();
await storage.write<String>('token', token);
final token = storage.read<String>('token');
From Hive
// Before (Hive)
await Hive.initFlutter();
final box = await Hive.openBox('myBox');
await box.put('token', token);
final token = box.get('token');
// After (LocalStorageClient)
final storage = LocalStorageClient.instance;
await storage.initialize();
await storage.write<String>('token', token);
final token = storage.read<String>('token');
Examples
Check the /example directory for comprehensive examples including:
- Basic operations
- Custom object storage
- Encryption
- Batch operations
- Reactive updates
- Error handling
- Testing setup
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
If you find this package helpful, please give it a ⭐ on GitHub!
For issues and feature requests, please use the GitHub issue tracker.
Changelog
See CHANGELOG.md for a list of changes.
Made with ❤️ for the Flutter community