keep 0.5.0
keep: ^0.5.0 copied to clipboard
Typed, reactive, encrypted local storage for Flutter.
Keep #
A type-safe, reactive local storage solution for Flutter with field-level encryption and hybrid storage support.
Features #
- Type-Safe Storage: Built-in support for primitives, collections, and custom objects
- Field-Level Encryption: Encrypt individual keys while keeping others in plain text
- Reactive: Stream-based updates and reactive widgets
- Hybrid Storage: Small data in memory-cached binary file for speed, large data in separate files to avoid memory overhead
- Dynamic Sub-Keys: Create nested keys without predefined schema
- Version-Based Migration: Automatic codec selection for seamless data format upgrades
- Extensible: Custom encryption, serialization, and storage adapters
Installation #
dependencies:
keep: ^0.3.0
Quick Start #
1. Define Storage Schema #
import 'package:keep/keep.dart';
class AppStorage extends Keep {
AppStorage() : super('app_v1');
final counter = Keep.kInt('counter');
final username = Keep.kString('username');
final settings = Keep.kMap('settings');
// Encrypted storage
final token = Keep.kStringSecure('auth_token');
}
final storage = AppStorage();
Keep ID & Multi-Instance #
Pass a unique id to the constructor. This ensures stable storage paths even after code obfuscation and allows running multiple isolated instances:
final user1 = AppStorage('user_1');
final user2 = AppStorage('user_2'); // Completely isolated
Initialization #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await storage.init();
runApp(const MyApp());
}
3. Read & Write #
// Write
await storage.counter.write(42);
// Read
final value = await storage.counter.read();
final valueOrDefault = await storage.counter.readSafe(0);
// Update
await storage.counter.update((current) => (current ?? 0) + 1);
// Remove
await storage.counter.remove();
4. Reactive UI #
Automatically rebuild widgets on value changes:
KeepBuilder<int>(
keepKey: storage.counter,
builder: (context, value) {
return Text('Counter: ${value ?? 0}');
},
)
5. Stream Listening #
storage.counter.stream.listen((key) async {
print('Counter changed: ${await key.read()}');
});
API Overview #
Storage Types #
// Primitives
Keep.kInt('key')
Keep.kString('key')
Keep.kBool('key')
Keep.kDouble('key')
// Collections
Keep.kList<T>('key')
Keep.kMap('key')
// Encrypted variants (add 'Secure' suffix)
Keep.kIntSecure('key')
Keep.kStringSecure('key')
// ... etc
// Custom types
Keep.custom<T>(
name: 'key',
fromStorage: (value) => /* deserialize */,
toStorage: (value) => /* serialize */,
)
Keep.customSecure<T>(
name: 'key',
fromStorage: (value) => /* deserialize */,
toStorage: (value) => /* serialize */,
)
Key Parameters #
| Parameter | Type | Description | Default |
|---|---|---|---|
name |
String |
Unique key identifier | Required |
removable |
bool |
Can be cleared with clearRemovable() |
false |
useExternal |
bool |
Store in separate file | false |
storage |
KeepStorage? |
Custom storage adapter | null |
KeepKey Methods #
// Reading
await key.read() // Returns T?
await key.readSafe(default) // Returns T with fallback
key.readSync() // Sync read (avoid for external storage)
// Writing
await key.write(value)
await key.update((current) => newValue)
// Management
await key.remove()
await key.exists // Future<bool>
key.existsSync // bool
// Reactivity
key.stream // Stream<KeepKey<T>>
Advanced Usage #
Sub-Keys #
Create nested keys dynamically:
final users = Keep.kString('users');
// Create sub-keys using call operator
final alice = users('alice');
await alice.write('Alice Data');
// Nested sub-keys
final aliceSettings = alice('settings');
await aliceSettings.write('Dark Mode');
// Chaining
await users('bob')('preferences').write('Minimal');
// Reading
final data = await alice.read(); // 'Alice Data'
Custom Encryption #
Implement KeepEncrypter for custom encryption:
import 'package:keep/keep.dart';
import 'package:encrypt/encrypt.dart' as crypt;
class AesEncrypter extends KeepEncrypter {
late crypt.Encrypter _encrypter;
final _iv = crypt.IV.fromLength(16);
@override
Future<void> init() async {
final key = crypt.Key.fromUtf8('my-32-char-key-here!!!!!!!!');
_encrypter = crypt.Encrypter(crypt.AES(key, mode: crypt.AESMode.gcm));
}
@override
String encryptSync(String data) => _encrypter.encrypt(data, iv: _iv).base64;
@override
String decryptSync(String data) => _encrypter.decrypt64(data, iv: _iv);
@override
Future<String> encrypt(String data) async => encryptSync(data);
@override
Future<String> decrypt(String data) async => decryptSync(data);
}
// Usage
class AppStorage extends Keep {
AppStorage() : super('secure_vault', encrypter: AesEncrypter());
final pin = Keep.kIntSecure('pin');
}
Custom Storage Adapter #
Implement KeepStorage to use custom backends:
class DatabaseStorage extends KeepStorage {
@override
Future<void> init(Keep keep) async {
// Initialize database
}
@override
Future<void> write(KeepKey key, Object? value) async {
// Store in database
}
@override
Future<V?> read<V>(KeepKey key) async {
// Read from database
return null;
}
@override
Future<void> remove(KeepKey key) async {
// Delete from database
}
@override
Future<bool> exists(KeepKey key) async => false;
@override
Future<void> clear() async {
// Clear all data
}
@override
Future<List<String>> getKeys() async => [];
@override
Future<void> removeKey(String storeName) async {}
@override
Future<void> clearRemovable() async {}
@override
Future<({String name, int flags, int version, KeepType type})?> readHeader(
String storeName,
) async => null;
@override
V? readSync<V>(KeepKey key) => null;
@override
bool existsSync(KeepKey key) => false;
}
// Usage
class AppStorage extends Keep {
AppStorage() : super('db_vault', externalStorage: DatabaseStorage());
final logs = Keep.kList('logs', useExternal: true);
}
Custom Serialization #
For complex objects:
class User {
final String name;
final int age;
User(this.name, this.age);
Map<String, dynamic> toJson() => {'name': name, 'age': age};
factory User.fromJson(Map json) => User(json['name'], json['age']);
}
final currentUser = Keep.custom<User>(
name: 'user',
fromStorage: (value) => value != null ? User.fromJson(value) : null,
toStorage: (user) => user.toJson(),
);
await currentUser.write(User('Alice', 30));
Error Handling #
class AppStorage extends Keep {
AppStorage() : super(
'error_logger',
onError: (e) {
print('Storage error: ${e.message}');
print('Key: ${e.key?.name}');
},
);
}
External Storage #
Use for large data to avoid memory overhead:
// Store large data in separate files
final bigData = Keep.kMap('large_dataset', useExternal: true);
final logs = Keep.kList('app_logs', useExternal: true);
Version-Based Migration #
Keep uses a version-based codec system for seamless storage format upgrades:
- Automatic Detection: Reads version byte and selects correct codec
- Backward Compatible: Old data remains readable
- Zero Downtime: Gradual migration as data is accessed
- Extensible: Add new codecs without breaking existing data
Current Format (V1): JSON-based with obfuscation
Future Formats: Binary serialization, compression, etc.
// Automatic codec selection
final codec = KeepCodec.of(bytes);
final entry = codec.decode();
See source code for codec implementation details.
Cleanup #
Clear All #
await storage.clear();
Clear Removable Only #
final cache = Keep.kString('cache', removable: true);
final temp = Keep.kMap('temp', removable: true);
// Later
await storage.clearRemovable(); // Only clears keys marked removable
Performance #
Benchmark results (1000 iterations):
| Operation | ops/sec |
|---|---|
| Internal Write | ~28K |
| Internal Read | ~110K |
| Internal ReadSync | ~1M |
| External Write | ~2.4K |
| External Read | ~125K |
| Secure Write | ~6K |
| Secure Read | ~150K |
Best Practices #
- Use
readSafe()instead ofread()when default values are acceptable - Avoid
readSync()for external storage (blocks UI thread) - Mark temporary data as
removable: true - Use external storage for data larger than 10KB
- Store encryption keys securely (e.g.,
flutter_secure_storage) - Always await
init()before first use
Example #
See example/lib/main.dart for a complete example.
cd example
flutter run
License #
MIT License - See LICENSE for details.
Developed by GeceGibi.