all_box 0.4.0 copy "all_box: ^0.4.0" to clipboard
all_box: ^0.4.0 copied to clipboard

Synchronous, lightweight key-value storage with crash-safe writes (write-ahead + atomic rename). Pure Dart, no Flutter dependency, no reactive layer.

all_box

πŸ‡§πŸ‡· PortuguΓͺs | πŸ‡ΊπŸ‡Έ English

pub version pub likes pub points license 77 tests

πŸ’‘ Simple, synchronous key-value storage for Dart and Flutter, with a crash-safe write strategy.

Table of contents #

πŸš€ Features #

  • πŸͺΆ Synchronous reads. After init(), every read<T>() is synchronous β€” no Future, no FutureBuilder.
  • 🧱 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 internal path_provider dependency, 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 real Timer.
  • 🌐 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:

  • path is always explicit on IO, automatic on Web. No internal directory resolution, no plugin dependency.
  • initialData only 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

[Performance comparison: all_box vs. Hive and SharedPreferences, measured on-device in profile mode]

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_interop Web 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 pub Reactive state for Flutter with zero dependencies β€” final count = 0.obs; + Observer(...).
all_validations_br pub Brazilian document validation (CPF, CNPJ, CNH, PIX), input formatters/masks, JWT/UUID/currency/encryption utilities.
all_image_compress pub Pure-Dart image compression (JPEG, PNG, GIF, BMP, TIFF, WebP), running in isolates.

πŸ‘₯ Contributors #

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.

5
likes
0
points
805
downloads

Publisher

verified publisheropensource.tatamemaster.com.br

Weekly Downloads

Synchronous, lightweight key-value storage with crash-safe writes (write-ahead + atomic rename). Pure Dart, no Flutter dependency, no reactive layer.

Repository (GitHub)
View/report issues

Topics

#storage #key-value #persistence #cache

License

unknown (license)

More

Packages that depend on all_box