kv_preferences
kv_preferences is a type-safe wrapper around SharedPreferences with:
- Runtime type checking and compile time when passed T
- Complex object parsing
- Versioning and migration
- Grouped keys support
- Testing mocks
๐ Index
- Motivation
- Getting Started
- Key API
- Writing with type enforcement
- Primitive types
- Complex types
- Invalidation
- Migration
- Grouped keys
- Testing
- Contributing
๐ก Motivation
SharedPreferences work but lack safety and structure. You can write a string and accidentally read it as an int โ no warnings, no errors.
kv_preferences makes your store safe by enforcing type expectations via KeyPreferences<T>.
๐ Getting Started
final store = KeyValueSharedPreferences();
await store.initialization(version: 1);
final key = KeyPreferences<String>('token');
await store.write<String>(key, 'abc123');
final token = store.read(key); // 'abc123'
๐ Key API
Define keys once and reuse them safely:
final nameKey = KeyPreferences<String>('name');
final ageKey = KeyPreferences<int>('age', description: 'User age', keyGroup: 'user');
โ๏ธ Writing with type enforcement
await store.write<String>(nameKey, 'Alice'); // OK
await store.write<int>(ageKey, 30); // OK
await store.write<int>(nameKey, 10); // Throws ArgumentError
โ ๏ธ Type safety is enforced only when explicitly passing
๐ข Primitive types
The following types are natively supported: String, int, double, bool, DateTime, List
๐งฉ Complex types
You must provide a parser for non-primitive types:
final userKey = KeyPreferences<User>(
'user',
valuePreferencesParser: (json) => User.fromJson(json as Map<String, Object?>),
);
// use it like this
await store.write<User>(KeyPreferencesGroup$Test.userKey, User.alice());
final user = store.read(KeyPreferencesGroup$Test.userKey)!;
print(user); // User(name: Alice, age: 30)
โ Your class must implement toJson() for encoding
@immutable
final class User {
const User({required this.name, required this.age});
factory User.fromJson(Map<String, dynamic> json) =>
User(name: json['name'] as String, age: json['age'] as int);
factory User.alice() => const User(name: 'Alice', age: 30);
///...
Map<String, dynamic> toJson() => <String, dynamic>{'name': name, 'age': age}; // <---
/// ...
}
๐ Invalidation
To clear all stored values:
await store.initialization(invalidate: true);
To selectively clear values:
await store.clear([
KeyPreferences<String>('token'),
KeyPreferences<int>('counter'),
]);
โฌ๏ธ Migration
To support store version upgrades:
class MyMigrator extends KeyValuePreferencesMigrator {
@override
Future<void> migrate(int fromVersion, int toVersion) async {
if (fromVersion == 1 && toVersion == 2) {
// perform migration
}
}
@override
Future<bool> needsMigration(int currentVersion) async {
return currentVersion < 2;
}
}
await store.initialization(
version: 2,
migrator: MyMigrator(),
);
Or in more complex case
class MultiStepMigrator extends KeyValuePreferencesMigrator {
final KeyValuePreferences store;
final List<int> stepsCalled = [];
MultiStepMigrator(this.store);
@override
Future<bool> needsMigration(int fromVersion) async => fromVersion < 5;
@override
Future<void> migrate(int fromVersion, int toVersion) async {
stepsCalled.add(fromVersion);
switch (fromVersion) {
case 1:
await store.write<int>(KeyPreferencesGroup$Test.intValue, 101);
break;
case 2:
await store.write<int>(KeyPreferencesGroup$Test.intValue, 102);
break;
case 3:
await store.write<int>(KeyPreferencesGroup$Test.intValue, 103);
break;
case 4:
await store.write<int>(KeyPreferencesGroup$Test.intValue, 104);
break;
}
}
}
โ If migration is required and no migrator is provided โ throws MigrationNotPassedException.
๐งญ Grouped keys
Group keys logically:
final emailKey = KeyPreferences<String>('email', keyGroup: 'user');
print(emailKey.groupedKey); // user.email
๐งช Testing
For testing you can use mockInitiazation
await store.mockInitiazation({
'name': 'Test User',
'age': 42,
});
๐ ๏ธ Contributing
Issues: github.com/4uzhoy/kv_preferences/issues
Pull requests: github.com/4uzhoy/kv_preferences/pulls
Your feedback is welcome.
Libraries
- kv_preferences
- A library for managing key-value preferences using shared preferences. It provides a structured way to define and access preferences with type safety. It includes support for complex types, versioning, and migration.