all_box
π§π· PortuguΓͺs | πΊπΈ English
π‘ Simple, synchronous key-value storage for Dart and Flutter, with a crash-safe write strategy.
Table of contents
- Features
- Installing
- Example App
- Usage examples
- Need reactivity?
- Separating data by user or context
- API
- How it works
- Comparison
- When to use it (and when not to)
- Testing
- Documentation
- Other packages by us
π Features
- πͺΆ Synchronous reads. After
init(), everyread<T>()is synchronous β noFuture, noFutureBuilder. - π§± Pure Dart, zero Flutter dependency. Works in any Dart environment β CLI, server, or Flutter app.
- π‘οΈ Crash-safe write strategy. Designed to avoid partially-written files on IO platforms. See How it works.
- π Explicit
path, never resolved internally. You decide where the container lives β no internalpath_providerdependency, so no plugin/Activity resolution surprises. - β‘ Optimistic, debounced writes, with opt-in stronger durability
tiers (
writeAndSave(),writeAndFlush()) when you need them. - π§ͺ In-memory storage for testing.
AllBox.memory()β no real I/O, no realTimer. - π Web support, backed by
window.localStorage. - π No built-in reactivity. Bring your own β see Need reactivity?.
Part of the all_* family of open-source packages alongside
all_validations_br
(Brazilian validations, utilities and encryption) and all_image_compress
(image compression).
π¦ Installing
dart pub add all_box
dependencies:
all_box: ^0.4.0
all_box is pure Dart and has a single entrypoint:
import 'package:all_box/all_box.dart';
// Web: no `path` needed β AllBox automatically uses window.localStorage.
final box = await AllBox.init('settings');
// IO (native VM/AOT, incl. Flutter mobile/desktop): pass a directory.
final box = await AllBox.init('settings', path: dir.path);
box.write('name', 'Carlos');
final name = box.read<String>('name');
Testing your own app/package against a real AllBox instance, with no real
I/O at all:
final box = await AllBox.memory('settings', initialData: {'darkMode': true});
π± Example App
The example/ directory contains an interactive Flutter app (CounterPage)
demonstrating the day-to-day public surface: optimistic write() vs.
writeAndFlush(), erase(), and flushNow() fired on
AppLifecycleState.paused.
cd example
flutter pub get
flutter run
π§ͺ Usage examples
Initialization
import 'package:all_box/all_box.dart';
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final dir = await getApplicationDocumentsDirectory();
await AllBox.init('my_container', path: dir.path);
runApp(const MyApp());
}
path is required on IO platforms and ignored on Web (AllBox never
resolves it for you). There's also an advanced storage: argument to plug
in your own AllBoxStorage implementation, but everyday code never needs
it.
Seeding data on first run
await AllBox.init(
'settings',
path: dir.path,
initialData: const {'darkMode': false, 'onboarded': false},
);
initialData only applies the first time a container is created β see
How it works for the exact rule.
Reading and writing
final box = AllBox('my_container');
box.write('name', 'Carlos'); // optimistic + debounced
String? name = box.read<String>('name');
String safeName = box.readOrDefault<String>('name', 'anonymous');
await box.writeAndSave('name', 'Carlos'); // waits for the OS write
await box.writeAndFlush('name', 'Carlos'); // waits for disk confirmation
box.remove('name');
box.erase(); // clears everything
await box.flushNow(); // forces a flush now, e.g. on AppLifecycleState.paused
Value with a safe fallback
final box = AllBox('settings');
final theme = box.readOrDefault<String>('theme', 'light');
// Returns 'light' if the 'theme' key doesn't exist yet
Updating a widget after a write
all_box has no built-in reactivity, so a widget that displays a stored
value re-reads it and calls setState right after writing:
class DarkModeSwitch extends StatefulWidget {
const DarkModeSwitch({super.key});
@override
State<DarkModeSwitch> createState() => _DarkModeSwitchState();
}
class _DarkModeSwitchState extends State<DarkModeSwitch> {
late bool _darkMode = AllBox().readOrDefault<bool>('darkMode', false);
void _toggle(bool value) {
AllBox().write('darkMode', value);
setState(() => _darkMode = value);
}
@override
Widget build(BuildContext context) {
return Switch(value: _darkMode, onChanged: _toggle);
}
}
Container introspection
box.hasData('theme'); // true / false
box.getKeys(); // every key ever written
box.getValues(); // every value ever written
Persisting app state when paused
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
AllBox('my_container').flushNow();
}
}
}
π Need reactivity?
all_box intentionally does not ship a reactive layer. It stays focused
on storage. If you want reactive state for Flutter, use
all_observer with
Observer(...) and keep storage and UI state separated β or wire all_box
into a ChangeNotifier/state-management solution you already use.
π§© Separating data by user or context
all_box doesn't ship a dedicated "scope", "namespace" or "collection" API
β that's on purpose, to keep the surface small. In real apps you still need
to separate local data by who it belongs to: the logged-in user, an
account, a gym, a company, a session, or just app-wide state. Two patterns
cover this well with the API that already exists.
A different container per context. AllBox.init(container, ...) takes
an arbitrary container name, and each name is a fully isolated storage β
its own file on IO, its own localStorage key on Web:
final appBox = await AllBox.init('app_settings', path: dir.path);
final userBox = await AllBox.init('user_$userId', path: dir.path);
Erasing or clearing one container never touches the other. This fits well when the number of contexts is small and known ahead of time β e.g. one container per logged-in user, plus one for app-wide settings.
Key prefixes inside a single container. When contexts are more dynamic, or you'd rather keep everything in one place, prefixing keys works just as well:
final userId = 'user_123';
box.write('user:$userId:theme', 'dark');
box.write('user:$userId:profile', profile);
final theme = box.read<String>('user:$userId:theme');
box.write('app:last_logged_user', userId);
box.write('app:language', 'pt-BR');
final language = box.read<String>('app:language');
A good practice is to separate keys by context. This helps avoid data conflicts and makes it easier to remove information from a specific user without deleting global app settings.
This separation is useful for:
- apps with multiple logged-in users on the same device;
- multi-tenant/SaaS apps (company, gym, organization);
- caching API responses per account;
- local preferences per user profile;
- safely wiping a user's data on logout, without touching global settings;
- keeping temporary session data apart from persistent app state.
For larger projects, consider centralizing key names in a dedicated class to avoid scattered strings across the app:
class StorageKeys {
static String userTheme(String userId) => 'user:$userId:theme';
static String userProfile(String userId) => 'user:$userId:profile';
static const appLanguage = 'app:language';
static const lastLoggedUser = 'app:last_logged_user';
}
Either pattern keeps all_box doing what it's meant for β preferences,
local settings, small app state and micro caches β not a replacement for a
full embedded database with queries, indexes or relations (see
When to use it).
π API
| Member | Description |
|---|---|
AllBox([container]) |
Factory constructor; returns a singleton per container name. |
static AllBox.init(container, {path, flushDelay, initialData, storage}) |
Loads container into memory and returns the initialized AllBox. path is required on IO platforms, ignored on Web. |
static AllBox.memory(container, {initialData}) |
Recommended way to test code that consumes all_box: no real I/O, no real Timer. Replaces the deprecated initWithMemoryBackendForTesting. |
T? read<T>(key) / T readOrDefault<T>(key, fallback) |
Synchronous reads. |
void write(key, value) |
Optimistic, debounced write. |
Future<void> writeAndSave(key, value) |
Writes and waits for the OS write to complete. |
Future<void> writeAndFlush(key, value) |
Writes and waits for the strongest durability guarantee available. |
void remove(key) / void erase() |
Removes a key / clears everything. |
Future<void> flushNow() |
Forces an immediate flush, bypassing the debounce window. |
hasData(key), getKeys(), getValues() |
Introspection. |
π οΈ How it works
all_box keeps a short list of deliberate design choices:
pathis always explicit on IO, automatic on Web. No internal directory resolution, no plugin dependency.initialDataonly ever applies on a genuine first run.- No built-in reactivity β see Need reactivity?.
The write-ahead + atomic-rename pipeline, flush/debounce coordination, the
dart:js_interop Web backend, and the full list of known limitations
(Web storage limits, isolate-safety, File.rename portability) are
documented in Internal architecture.
βοΈ Comparison
all_box |
GetStorage | Hive | Isar | SharedPreferences | |
|---|---|---|---|---|---|
| Reads | Synchronous, in memory | Synchronous, in memory | Synchronous (open box) | Synchronous (simple) / async (queries) | Async |
Storage path |
Explicit, required | Resolved internally | Resolved by caller | Resolved by caller | Resolved by platform |
| Crash-safety strategy | Write-ahead + atomic rename + .bak, documented |
Not documented at the same level | Internal WAL/compaction | WAL via its own engine | Platform-dependent |
| Web support | Yes (localStorage) |
Yes | Yes | Yes | Yes |
| Reactivity | None (bring your own) | GetBuilder/Obx (GetX) |
ValueListenableBuilder over box.listenable() |
watchObject/watchLazy (streams) |
None β needs your own wrapper |
| Scope | Key-value storage only | Storage + some UI utils (GetX) | Box-oriented storage | Full database | Platform wrapper |

Measured on-device (Android, profile mode) via the example app's "Storage comparison" screen. Full methodology, numbers, and per-library caveats in Comparison.
all_box intentionally doesn't try to be a database or resolve its own
path β that's a design choice, not a gap.
π€ When to use it (and when not to)
Reach for all_box when you want simple key-value storage β settings,
flags, small app state β with synchronous reads after boot and optimistic
writes with an explicit opt-in to durable confirmation. Bring your own
reactivity (see above) if you need it.
Reach for something else when you specifically need what it specializes in: custom type adapters for complex objects (Hive), a full embedded database with queries/indexes/relations (Isar), the Flutter ecosystem's most "standard" platform wrapper (SharedPreferences), or a storage library with built-in reactivity.
π§ͺ Testing
flutter test
If you're testing your own app/package (not all_box itself), use
in-memory storage instead of a real directory or browser:
final box = await AllBox.memory(
'my_container',
initialData: {'darkMode': true},
);
This does no real I/O and schedules no real Timer, which matters
specifically inside testWidgets (its FakeAsync zone expects every
Timer to resolve before the test ends).
(The older AllBox.initWithMemoryBackendForTesting() still works β it's
now a thin, @Deprecated wrapper around AllBox.memory().)
π Documentation
- Comparison β detailed comparison vs. GetStorage, Hive, Isar, SharedPreferences, including a performance benchmark.
- Internal architecture β write-ahead + atomic rename pipeline, flush coordination, the
dart:js_interopWeb backend, and known limitations.
π¦ Other packages by us
all_box is part of a small family of Dart & Flutter packages published
under the
opensource.tatamemaster.com.br
verified publisher:
| Package | Version | Description |
|---|---|---|
all_observer |
Reactive state for Flutter with zero dependencies β final count = 0.obs; + Observer(...). |
|
all_validations_br |
Brazilian document validation (CPF, CNPJ, CNH, PIX), input formatters/masks, JWT/UUID/currency/encryption utilities. | |
all_image_compress |
Pure-Dart image compression (JPEG, PNG, GIF, BMP, TIFF, WebP), running in isolates. |
π₯ Contributors
Made with contrib.rocks.
Contributions are welcome! Read CONTRIBUTING.md to get started.
Issues and pull requests are welcome at the GitHub repository. Distributed under the MIT license.
Libraries
- all_box
- AllBox: a synchronous, lightweight and fast key-value storage, with
crash-safe writes. Pure Dart, with no reactive layer β
write(),remove()anderase()only update memory and schedule persistence; they never notify anything. - all_box_flutter