Boxx
Boxx is versioned key-value storage for Flutter on Android, iOS, Linux, macOS, Windows, and web. It supports exact JSON value persistence, named namespaces, change streams, and optional authenticated encryption.
Requirements
- Dart 3.8.1 or later
- Flutter 3.32.0 or later
- JSON-encodable values
Install
dependencies:
boxx: ^0.3.0
Basic usage
import 'package:boxx/boxx.dart';
final box = Boxx(
name: 'application-settings',
mode: EncryptionMode.none,
);
await box.initialize();
await box.put('theme', 'dark');
final theme = await box.get<String>('theme');
await box.delete('theme');
box.dispose();
Operations initialize lazily, but explicit initialization is recommended so an unavailable storage backend fails during application startup.
Authenticated encryption
AES mode uses AES-256-GCM. Fernet mode uses authenticated Fernet tokens. Both derive per-record keys with PBKDF2-HMAC-SHA256 and a random salt.
Supply at least 32 UTF-8 bytes of high-entropy random key material. Store the key in platform-backed secure storage. Do not embed it in source code, build arguments, or environment files shipped with the application.
final vault = Boxx(
name: 'user-vault',
mode: EncryptionMode.aes,
encryptionKey: keyLoadedFromSecureStorage,
);
await vault.put('profile', {'name': 'Alice', 'roles': ['support']});
final profile = await vault.get<Map<String, dynamic>>('profile');
Encrypted modes fail at construction when the key is absent or shorter than 32 UTF-8 bytes. Boxx never silently falls back to plaintext.
Namespaces
The name parameter isolates independent stores. Use a stable name and do not
reuse a namespace with different current encryption settings.
final preferences = Boxx(name: 'preferences', mode: EncryptionMode.none);
final cache = Boxx(name: 'cache', mode: EncryptionMode.none);
clear() affects only versioned records in the instance namespace. Legacy
pre-0.3.0 records are removed individually after they are read and migrated;
Boxx never bulk-deletes root-level files it cannot identify safely.
Values and keys
Boxx accepts JSON values: strings, numbers, booleans, null, lists, and maps with string keys. Types are preserved across application restarts. Storage keys may contain path separators, punctuation, or Unicode and are limited to 4096 UTF-8 bytes.
await box.put('literal-number', '42');
await box.put('counter', 42);
final keys = await box.keys;
final values = await box.values;
final entries = await box.all();
Requesting the wrong generic type throws BoxxTypeMismatchException.
Change streams
final subscription = box.watch<String>('status').listen((status) {
// The current value is emitted first, followed by committed changes.
});
Changes are shared between Boxx instances in the same Dart isolate. On web they
are also propagated to same-origin tabs through BroadcastChannel. Native
changes performed outside the process are visible on the next read but cannot
produce an in-process stream event.
Legacy migration
Version 0.3.0 reads known pre-v2 keys in the default namespace and rewrites each
record into the current format when it is read. Legacy records are not included
in keys until migrated because old filenames cannot safely preserve original
keys. Applications should read their known keys explicitly. When changing
encryption mode or key, provide the previous settings:
final box = Boxx(
mode: EncryptionMode.aes,
encryptionKey: newKey,
legacyMode: EncryptionMode.fernet,
legacyEncryptionKey: oldKey,
);
Legacy string values such as "42" were stored ambiguously by versions before
0.3.0 and may already decode as a number. That lost type information cannot be
recovered automatically; validate migrated application data before removing the
old application version.
Diagnostics and errors
Boxx does not log keys or values. Connect the diagnostics callback to the host application's logging or error-reporting system:
final box = Boxx(
mode: EncryptionMode.none,
diagnostics: (event) {
logger.error(event.operation, event.error, event.stackTrace);
},
);
Stable exception categories include BoxxConfigurationException,
BoxxEncryptionException, BoxxCorruptDataException, BoxxStorageException,
BoxxTypeMismatchException, and BoxxDisposedException.
See SUPPORT.md for recovery and troubleshooting and RELEASE.md for release and rollback procedures.
License
BSD 3-Clause. See LICENSE.