an_reactive_state
An industrial-grade, call-stack synchronous reactive graph engine (Signals) for pure Dart and Flutter, built for ultimate evaluation performance and absolute memory safety.
an_reactive_state discards the heavy macro-task/micro-task scheduling overhead found in
traditional asynchronous stream models (such as Stream or Kotlin Flow). Instead, it runs entirely on
an ultra-fast CPU Call-Stack synchronous reactive topology. Deeply integrated with the
Cancellable lifecycle tree, it fundamentally eliminates async context interleaving bugs and memory
leaks.
🌟 Key Killer Features
- ⚡ Explicit State Roles: Separates reactive nodes into
RState<T>(writable atoms) andComputedState<T>(read-only derivations). This architecture ensures strict data flow control and prevents accidental writes to calculated nodes at the compiler level. - ⛓️ Linked-List Evaluation Context: Replaces expensive dynamic array reallocations with a lightweight, doubly-linked-list node switching architecture. PUSH/POP operations for reactive tracking run at a strict, deterministic O(1) time complexity.
- 🛡️ Zone-Safe Async Isolation: Leverages Dart's native
Zonelocal storage mechanism. When multiple asynchronous tasks interleave and modify states concurrently, the dependency tracking and evaluation scope remain strictly isolated per execution branch, preventing global state pollution. - 🛑 Zero-Leak Lifecycle Self-Destruction: Fully bound to the
Cancellabletoken tree. When a host container is disposed of, all parent-child listener registrations are permanently purged within a single atomic microsecond, leaving zero memory overhead. - 🚫 Circular Dependency Watchdog: Before entering the call-stack evaluation domain, it crawls
upwards from the current node to check its ancestors. If a dependency loop is detected, it throws
a transparent
StateErrorimmediately, eliminating any risk of Stack Overflow crashes. - 🔍 Lazy Evaluation & Memoization: Implements a highly efficient "Dirty Propagation" mechanism.
Derived computation states do not calculate values eagerly upon initialization. They run only when
a downstream node or listener explicitly invokes
get valueoraddListener, caching the output until upstreams flag it as dirty again. - ⚖️ Equality Guard: Supports custom equality comparers. Even if your calculation closure
generates a brand-new instance (such as a fresh
Listor object mapping), it will block downstream propagation if the content remains identical, completely terminating downstream evaluation ripples. - 📦 Synchronous Batching & Untracking: Provides global
batchanduntrackedclosures. You can execute multiple rapid updates inside abatchblock while ensuring downstream effects are triggered exactly once, executing synchronously right before exiting the block—with no microtask latency.
📦 Installation
Add the following dependencies to your pubspec.yaml:
dependencies:
an_reactive_state: ^1.2.1
cancellable: ^2.6.0 # The foundation of industrial lifecycle management
🚀 Core Usage Guide
1. Basic Read/Write, Derived State, and Side Effects (effect)
In an_reactive_state, raw state sources and high-order calculation pipelines share a uniform
interface. Note that the cancellable parameter is optional across all state constructors and effect().
import 'package:cancellable/cancellable.dart';
import 'package:an_reactive_state/an_reactive_state.dart';
void main() {
final scope = Cancellable();
// 1. Declare raw state atoms (Sources)
final price = RState<double>(initialValue: 99.0);
final count = RState<int>(initialValue: 1);
// 2. Declare a high-order derived calculation state (Computed)
final totalPrice = ComputedState<double>(
cancellable: scope,
computer: () => price.value * count.value, // Automatically tracks price and count
);
// 3. Register a side effect: whatever reactive states it reads inside the block,
// it will automatically re-run whenever those states change.
effect(() {
print('【UI Effect Card】Current total checkout amount: \${totalPrice.value}');
}, cancellable: scope);
print('--- Modifying Quantity ---');
count.value = 3; // Executed synchronously inside the current call stack
scope.cancel(); // Everything self-destructs; subsequent writes are safely blocked.
}
2. Postponing Notifications (batch) & Reading Without Tracking (untracked)
Wrap multiple independent value adjustments inside a batch to prevent computational jitter. Use
untracked to pull snapshots of data safely without contaminating the active evaluation graph.
void main() {
final scope = Cancellable();
final width = RState<int>(initialValue: 10, cancellable: scope);
final height = RState<int>(initialValue: 5, cancellable: scope);
final isLogging = RState<bool>(initialValue: true, cancellable: scope);
final areaReport = ComputedState<String>(
cancellable: scope,
computer: () {
// Core control: Read the switch silently inside an untracked closure.
// This ensures areaReport does NOT establish a dependency on isLogging!
final logActive = untracked(() => isLogging.value);
if (logActive) {
untracked(() => print('【System Internal】Evaluating reactive matrix area...'));
}
return 'Current Area: \${width.value * height.value} sq.m';
},
);
areaReport.addListener(() => print('【External Screen】Updated -> \${areaReport.value}'));
print('\n--- Scenario: Triggering Batch Updates ---');
batch(() {
print('(Starting sequential assignments inside batch...)');
width.value = 20; // Upstream flags change, downstream evaluation holds
height.value = 10; // Upstream flags change, downstream evaluation holds
print('(Exiting batch scope shortly)');
});
// Downstream triggers EXACTLY ONCE on the call-stack right at the block exit!
scope.cancel();
}
🏗️ Domain Lifecycle Management
In true production-grade architectures, wrapping your reactive states inside a manageable domain controller gives you a clean state tree with absolute memory safety.
import 'package:cancellable/cancellable.dart';
import 'package:an_reactive_state/an_reactive_state.dart';
class OrderCartManager {
// Establish a single master cancellable token for this business module
final Cancellable _bag = Cancellable();
late final RState<double> productPrice;
late final RState<int> quantity;
late final ComputedState<double> finalPay;
OrderCartManager() {
productPrice = RState<double>(initialValue: 15.0, cancellable: _bag);
quantity = RState<int>(initialValue: 1, cancellable: _bag);
finalPay = ComputedState<double>(
cancellable: _bag,
// Inject Equality Guard to ensure content matching blocks computational duplication
equals: (a, b) => a == b,
computer: () => productPrice.value * quantity.value,
);
// Bind automated domain side-effects
effect(() {
print('【Domain Event Report】Current final payment amount flowed to: \${finalPay.value}');
}, cancellable: _bag);
}
void addItems(int countToAdd) {
quantity.value += countToAdd;
}
// Explicitly wipe bindings completely when this manager clears up
void dispose() {
print('【Architecture Lifecycle】Manager is clearing; triggering global state graph flush!');
_bag.cancel(); // All map registries and closures are cleared in 0μs, guaranteeing 0 leaks.
}
}
🛠️ Functional Advanced Trick: Freeze / Once Memoization
You do not even need to extend the internal methods of ComputedState. Thanks to our highly sensitive
Dual-Buffered Graph Diffing algorithm, a developer can write a self-rewriting closure inside the
computer to achieve an auto-detaching one-time snapshot that calculates only once, breaks all
parent connections, and permanently freezes itself:
final ComputedState<String> lazyAndFrozenConfig = ComputedState<String>(
cancellable: scope,
computer: () {
String? snapshot;
return () {
if (snapshot != null) {
// Ensuing access pathways drop into the memoized cache branch.
// Because no upstreams (like remoteIp.value) are invoked here,
// our dynamic graph diff engine automatically realizes those parent dependencies
// are no longer needed, permanently severing listeners from the parent list!
return untracked(() => snapshot!);
}
print('【Extreme Optimization】Executing heavy configuration parsing exactly once...');
snapshot = 'Server Endpoint: IP=\${remoteIp.value}, PORT=\${remotePort.value}';
return snapshot!;
};
}(), // Immediately run the outer closure to bind the self-detaching inner function
);
💡 Advanced Features & Extensions
Collections & In-Place Mutation
RState and ComputedState provide built-in extension methods for list/map operations and in-place state mutation:
// Direct list & map indexing operators
final list = RState<List<int>>(initialValue: [10, 20]);
print(list[0]); // 10
final map = RState<Map<String, int>>(initialValue: {'a': 1});
map['b'] = 2; // Automatically triggers reactive update
// In-place mutation with automatic notification
list.mutate((l) => l.add(30));
// Manual refresh trigger
list.refresh();
Flexible Lifecycle Trees (makeLiveCancellable)
In addition to passing cancellable in constructors, state nodes and side effect instances expose makeLiveCancellable() to dynamically derive child token scopes with parent linkage and weak reference configuration:
final liveToken = state.makeLiveCancellable(
other: customParentScope,
weakRef: true,
);
Issues
If you encounter issues, here are some tips for debug, if nothing helps report to issue tracker on GitHub: