kaeru_store
A simple and powerful persistence library for Flutter that seamlessly integrates with the kaeru reactive state management package. kaeru_store allows you to effortlessly persist your application's state, with built-in support for data encryption.
Features
- Easy to Use: Define stores and persist their state with minimal boilerplate.
- Reactive: Built on
kaeru, your UI automatically updates when the store's data changes. - Secure: Built-in AES encryption to protect sensitive user data.
- Cross-Platform: Works on mobile, desktop, and web.
- Customizable: Provides the ability to implement custom storage backends (e.g., SQLite, ObjectBox, secure storage).
Getting Started
1. Add Dependencies
Add kaeru and kaeru_store to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
kaeru: ^1.0.0 # Use the latest version
kaeru_store: ^1.0.0 # Use the latest version
2. Create a Store
Create a class that extends Store. Define your state variables using Ref and specify which ones to persist by overriding the persistents method.
// lib/stores/counter_store.dart
import 'package:kaeru/kaeru.dart';
import 'package:kaeru_store/kaeru_store.dart';
class CounterStore extends Store {
// A unique ID for the store. This is used as the filename.
@override
String get id => 'counter';
// Define a reactive state variable with an initial value of 0.
late final count = Ref<int>(0);
// Specify that the `count` variable should be persisted.
// The order in this list is important and should not be changed.
@override
List<Ref> persistents() => [count];
}
// Create a global instance of your store.
final counterStore = CounterStore();
3. Use the Store in Your UI
Use Watch (from kaeru) to listen to changes in your store and automatically rebuild your widget.
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:kaeru/kaeru.dart';
import 'stores/counter_store.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Kaeru Store Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Use Watch to rebuild the Text widget whenever count changes.
Watch(() => Text(
'Count: ${counterStore.count.value}',
style: Theme.of(context).textTheme.headlineMedium,
)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => counterStore.count.value++,
child: const Text('Increment'),
),
],
),
),
),
);
}
}
Data Encryption
kaeru_store provides built-in AES encryption. If a password is provided, all data will be encrypted before being written to storage.
Global Password
You can set a global password that will be used for all stores. Set Store.privateKey before your app runs.
void main() async {
// Set a global password for all stores.
Store.privateKey = 'my_super_secret_password';
runApp(const MyApp());
}
If Store.privateKey is an empty string (the default), encryption is disabled.
Per-Store Password
You can also specify a password for an individual store by overriding the privateKeyScope getter. This will override any global password for that specific store.
class SecureNotesStore extends Store {
@override
String get id => 'secure_notes';
// This store will use its own password.
@override
String get privateKeyScope => 'a_different_password_for_notes';
late final notes = Ref<List<String>>([]);
@override
List<Ref> persistents() => [notes];
}
Preloading Data
When an app starts, kaeru_store begins restoring data from storage in the background. If you need to ensure that data is available before your UI is displayed (e.g., to avoid a flicker), you can use the Store.preload() method.
Call this method in your main function before runApp().
void main() async {
// Ensure the Flutter binding is initialized.
WidgetsFlutterBinding.ensureInitialized();
// Wait for counterStore and any other stores to finish restoring data.
await Store.preload([counterStore]);
runApp(const MyApp());
}
Advanced Configuration
For more fine-grained control, you can customize the following properties:
Store.dir
This static property sets the directory where store files are saved. The default is {document}/kaeru_store, where {document} is the application's documents directory.
void main() {
// Change the default storage directory
Store.dir = '{document}/my_app_data';
runApp(const MyApp());
}
Store.salt
This static property defines the salt used for deriving the encryption key from your password. Changing the salt enhances security. It's recommended to set a custom salt for your application.
void main() {
// Set a custom salt for key derivation
Store.salt = 'my_unique_app_salt';
Store.privateKey = 'my_super_secret_password';
runApp(const MyApp());
}
filename
You can override the filename getter in your store to change the default file name, which is [id].data by default.
class UserSettingsStore extends Store {
@override
String get id => 'user_settings';
// Customize the filename for this store
@override
String get filename => 'user/settings.json';
// ...
}
Custom Storage Implementation
By default, kaeru_store uses the local file system on mobile/desktop and localStorage on the web. You can create your own storage backend by implementing the StorageBase abstract class.
This is useful for integrating with databases like SQLite, ObjectBox, or using secure storage APIs like flutter_secure_storage.
Example: Custom SQLite Storage
Here is a conceptual example of how you might create a storage backend using a database.
1. Implement StorageBase
Create a class that implements the get and set methods.
import 'dart:typed_data';
import 'package:kaeru_store/kaeru_store.dart';
import 'package:sqflite/sqflite.dart'; // Example dependency
class SQLiteStorage implements StorageBase {
final Future<Database> _db;
SQLiteStorage(this._db);
@override
Future<Uint8List> get(String path) async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'store',
columns: ['data'],
where: 'id = ?',
whereArgs: [path],
);
if (maps.isNotEmpty) {
// In a real implementation, you would handle the case where `data` is null
// and decode it correctly. Here we assume it's a blob.
return maps.first['data'] as Uint8List;
}
return Uint8List(0); // Return empty list if not found
}
@override
Future<void> set(String path, Uint8List data) async {
final db = await _db;
await db.insert(
'store',
{'id': path, 'data': data},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
2. Configure Your Store to Use It
You can set your custom storage implementation globally or on a per-store basis.
Global Configuration:
Set the Store.storage static property before your app runs.
void main() async {
// Initialize your database
final db = openDatabase('kaeru_store.db', onCreate: (db, version) {
return db.execute(
"CREATE TABLE store(id TEXT PRIMARY KEY, data BLOB)",
);
}, version: 1);
// Set your custom storage as the global default
Store.storage = SQLiteStorage(db);
await Store.preload([counterStore]);
runApp(const MyApp());
}
Per-Store Configuration:
Override the storageScope getter in a specific store.
class MyCustomStoredData extends Store {
@override
String get id => 'my_custom_data';
// This store uses a different storage backend
@override
StorageBase get storageScope => MySpecialStorage(); // Your custom implementation
// ...
}
Libraries
- encrypt
- kaeru_store
- The kaeru_store library provides a simple way to persist and manage Flutter
application state. It is built on top of
kaerufor reactive state management and provides automatic data encryption capabilities. - storage/base
- storage/default/default
- storage/default/default.io
- storage/default/default.stub
- storage/default/default.web
- store