kv_preferences

Pub License: Apache Code size


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

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.