blackbox 0.7.1 copy "blackbox: ^0.7.1" to clipboard
blackbox: ^0.7.1 copied to clipboard

Deterministic reactive computation core with explicit dependency graphs, boxes, and flows. Designed for testable business logic and state pipelines.

blackbox #

Reactive state management core for Dart.

Boxes hold state. Graphs wire dependencies. No code generation. No boilerplate.

See the full documentation with examples: README

Quick Start #

import 'package:blackbox/blackbox.dart';

class CounterBox extends NoInputBox<int> {
  int _value = 0;

  @override
  int compute(int? previous) => _value;

  void inc() => action(() => _value++);
}

API #

Box Types #

Class Input Sync/Async
NoInputBox<O> none sync
Box<I, O> yes sync
NoInputAsyncBox<O> none async
AsyncBox<I, O> yes async

Graph #

final graph = Graph.builder()
    .add(boxA)
    .add(boxB, input: (d) => d.whenReady(boxA))
    .build(start: true);

Effects #

Graph.builder()
    .add(checkoutState)
    .addEffect<CheckoutState>(
      (d) => d.whenReady(checkoutState),
      run: (current, previous) {
        if (previous is! CheckoutSuccess && current is CheckoutSuccess) {
          cart.clear();
        }
      },
    )
    .build(start: true);

Effects are explicit graph sinks:

  • they run only when their input changes
  • they receive current and previous
  • async handlers are fire-and-forget

Persistence #

Add a mixin to any box — implement persistKeyFor() and you're done:

// Sync box
class ThemeBox extends NoInputBox<String> with Persisted<void, String> {
  @override
  String persistKeyFor(void _) => 'theme';
  // ...
}

// Async box with persistence only
class UserBox extends AsyncBox<String, User>
    with AsyncPersisted<String, User> {
  @override
  String persistKeyFor(String id) => 'user:$id';
  // ...
}

// Async box with persistence + managed cache
class CachedUserBox extends AsyncBox<String, User>
    with AsyncPersisted<String, User>, AsyncManagedCache<String, User> {
  @override
  String persistKeyFor(String id) => 'user:$id';

  @override
  Duration get cacheTtl => Duration(minutes: 5);
  // ...
}

// Sync box with async fetch (always-available value, background refresh)
class StopListBox extends Box<String, StopList>
    with ManagedCache<String, StopList> {
  StopListBox(String input) : super(input, initialValue: StopList.empty);

  @override
  Duration get cacheTtl => Duration(seconds: 60);

  @override
  Future<StopList> fetch(String input) => api.getStopList(input);
}

Initialize persistence once before creating persistent boxes:

BlackboxPersistence.init(store, codecs: [UserJsonCodec()]);

Built-in codecs exist for int, double, String, and bool.

Mixin For Features
Persisted<I, O> Box, NoInputBox save/restore
AsyncPersisted<I, O> AsyncBox, NoInputAsyncBox save/restore
ManagedCache<I, O> on Box sync value + background fetch, TTL, fail-open, refresh(), invalidateCache()
AsyncManagedCache<I, O> on AsyncBox, NoInputAsyncBox TTL, stale-while-refresh, refresh(), invalidateCache() (works standalone or composed with AsyncPersisted)

Both cache mixins dedupe concurrent refresh() calls per box instance. Compose with Persisted / AsyncPersisted to persist cached values across restarts — the disk value wins over initialValue on boot.

In Flutter and Jaspr apps, prefer the platform adapters:

  • await SharedPrefsStore.preload() from blackbox_flutter
  • await LocalStorageStore.preload() from blackbox_jaspr

Lifecycle #

  • resolveInitialValue(I input, O? initialValue) — seed the initial state before the first compute
  • onFirstCompute(I input, O? previous) — called once before the first compute
  • beforeCompute(I input, O? previous) — optionally short-circuit compute() with a ready Future
  • onReady() — called after initialization, before the first recompute
  • dispose() — called by graph.dispose()
  • action(() { ... }) — mutate state and trigger recomputation
  • await action(() async { ... }) — for async boxes, completes after the recompute finishes

License #

MIT

2
likes
140
points
731
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Deterministic reactive computation core with explicit dependency graphs, boxes, and flows. Designed for testable business logic and state pipelines.

Repository (GitHub)
View/report issues

Topics

#reactive #state-management #data-flow #dependency-graph #computation-graph

License

MIT (license)

Dependencies

meta

More

Packages that depend on blackbox