Simple Persistence
A small package to easily store objects on disk.
Features
- Easily convert classes into storable classes
- No code-generation
- Multiple storage strategies for different applications
- No dependencies
Table of Contents
Setup
The setup is quite easy and requires three steps:
- Step 1: Create a
Storable
class - Step 2: Register deserializer
- Step 3: Create a
Store
Create a Storable
class
The first step is to make your class a Storable
. You will need to implement the data
getter and a function to create the object from a map (i.e. fromMap
). In case you implement a copyWith method, make sure that you are copying the id
field as well (as not make it be modifiable). This is important if you want to be able to update an object.
class User extends Storable {
final String name;
@override
Map<String, dynamic> get data => {
'name': name,
};
User.fromMap(Map<String, dynamic> map) : name = map['name'];
}
Now the User
class is already done and prepared to be saved and loaded.
Register deserializer for your Storable
The next step is to register your class with the PersistenceManager
singleton instance so it knows how to deserialize it:
PersistenceManager.I.register(User.fromMap);
After this, the StorableFactory.deserialize()
function will be able to automatically figure out which class you want to deserialize if you give it the serialized data and return a User
object.
Freezed
When the class of the object you are working with is not public, you can register the runtimeType instead:
@freezed
class User extends Storable with _$User {
factory User.johnDoe() = _JohnDoeUser;
// ...
}
// The actual type of the object returned from `User.johnDoe()` is hidden away in the
// code generated by freezed, so we use this workaround.
PersistenceManager.I.registerRuntimeType(
instance: User.johnDoe(),
deserializer: User.fromJson,
);
Create a Store
for your Storable
The last step is to create a Store
that is responsible for the read/write operations. Here you have choose between two different strategies: JsonFileStore
will store all objects in one single file and BigJsonStore
will have a separate file for every object (useful if you have a lot of data).
final userStore = JsonFileStore<User>(
// This needs to be a path to a JSON file. It can be a [Future] as well. In Flutter it could e. g. be based on `getApplicationDocumentsDirectory()` from the path_provider package.
path: '/.../.../test.json',
// This needs to have the deseralizer for the correct type (in this case [User]) registered.
storableFactory: sf,
);
And that's it. You can now use the operations on the store to persist your data.
Putting it together
All together a simple user.dart
could look like this:
final sf = StorableFactory()
..registerDeserializer(User.fromMap);
class User extends Storable {
static final store = JsonFileStore<User>(
path: '/.../.../user.json',
storableFactory: sf,
);
final String name;
User(this.name);
@override
Map<String, dynamic> get data => {
'name': name,
};
User.fromMap(Map<String, dynamic> map) : name = map['name'];
User copyWith({String? name}) =>
User(name ?? this.name)
..id = id; // copying the id is IMPORTANT, otherwise a new object will be created in the store.
}
Usage
After the example setup from above you can now easily persist and load data in your app:
void main() {
var user = User('John Doe');
// save user
user = User.store.save(user); // the id will be set on the returned value
// get user
User.store.get(user.id);
// update user
final newUser = user.copyWith(name: 'Jane Doe');
User.store.save(newUser);
// get a stream of user updates
// The stream will emit an update everytime the data for that id changes and will be
// closed when the object is deleted.
final stream = User.store.listen(user.id);
// delete user
User.store.delete(user.id);
}
KVStore
The KVStore class can be used to store simple key-value pairs. Each KVStore instance has a prefix that separates the key spaces.
void main() {
// You need to initialize with a directory before creating any store
KVStore.init('/.../.../');
final kvs = KVStore('settings');
// Usage
// you can save any Type that can be automatically converted to JSON
kvs.save('bool', true);
kvs.save('int', 123);
kvs.save('double', 123.45);
kvs.save('string', 'the quick brown fox');
kvs.save('list', [1, 2, 3]);
kvs.get<int>('int'); // 123
kvs.get<double>('int'); // 123.00
kvs.delete('int');
kvs.clear();
}