davianspace_reactive 1.0.0
davianspace_reactive: ^1.0.0 copied to clipboard
High-performance reactive state management for Dart and Flutter using signals, computed values, effects, batching, async state, and dependency injection.
// Example code intentionally uses print for demonstration.
// ignore_for_file: avoid_print
import 'dart:async';
import 'package:davianspace_dependencyinjection/davianspace_dependencyinjection.dart';
import 'package:davianspace_reactive/src/async/async_reactive.dart';
import 'package:davianspace_reactive/src/async/resource.dart';
import 'package:davianspace_reactive/src/collections/reactive_list.dart';
import 'package:davianspace_reactive/src/collections/reactive_map.dart';
import 'package:davianspace_reactive/src/collections/reactive_set.dart';
import 'package:davianspace_reactive/src/core/batch.dart';
import 'package:davianspace_reactive/src/core/computed.dart';
import 'package:davianspace_reactive/src/core/effect.dart';
import 'package:davianspace_reactive/src/core/reactive.dart';
import 'package:davianspace_reactive/src/core/reactive_error.dart';
import 'package:davianspace_reactive/src/devtools/debug_logger.dart';
import 'package:davianspace_reactive/src/devtools/graph_inspector.dart';
import 'package:davianspace_reactive/src/di/reactive_service_extensions.dart';
import 'package:davianspace_reactive/src/extensions/stream_extensions.dart';
// =============================================================================
// Example 1 — Core signals and computed values
// =============================================================================
/// Demonstrates the fundamental reactive primitives.
void basicReactiveExample() {
print('=== Example 1: Core signals ===\n');
// Create reactive signals
final firstName = Reactive<String>('John', name: 'firstName');
final lastName = Reactive<String>('Doe', name: 'lastName');
// Derive a computed value — automatically tracks dependencies
final fullName = Computed<String>(
() => '${firstName.value} ${lastName.value}',
name: 'fullName',
);
// Create an effect — runs whenever dependencies change
final fx = Effect(
() => print(' Full name: ${fullName.value}'),
name: 'namePrinter',
);
// Output: Full name: John Doe
// Update state — triggers recomputation and effect
firstName.value = 'Jane';
// Output: Full name: Jane Doe
lastName.value = 'Smith';
// Output: Full name: Jane Smith
// Clean up
fx.dispose();
fullName.dispose();
firstName.dispose();
lastName.dispose();
print('');
}
// =============================================================================
// Example 2 — Batching and coalesced updates
// =============================================================================
/// Demonstrates batch updates for efficiency.
void batchingExample() {
print('=== Example 2: Batching ===\n');
final counter = Reactive<int>(0, name: 'counter');
var notifyCount = 0;
final sub = counter.subscribe(() {
notifyCount++;
print(' Notified (#$notifyCount): ${counter.peek()}');
});
// Without batching: each update fires a notification
counter.value = 1; // Notified (#1)
counter.value = 2; // Notified (#2)
// With batching: all updates coalesced into one notification
ReactiveBatch.run(() {
counter.value = 10;
counter.value = 20;
counter.value = 30;
print(' Inside batch — no notifications yet');
});
// Notified (#3): 30
print(' Total notifications: $notifyCount');
sub.dispose();
counter.dispose();
print('');
}
// =============================================================================
// Example 3 — Lifecycle hooks and freezing
// =============================================================================
/// Demonstrates lifecycle hooks, freezing, and snapshots.
void lifecycleExample() {
print('=== Example 3: Lifecycle ===\n');
final score = Reactive<int>(100, name: 'score');
// Register lifecycle hooks
score.onChange((newValue) => print(' onChange: $newValue'));
score.onDispose(() => print(' onDispose: score cleaned up'));
print(' State: ${score.lifecycleState}'); // created
score.value; // reading transitions to active
print(' State: ${score.lifecycleState}'); // active
// Take a snapshot
final snap = score.snapshot();
print(' Snapshot: ${snap.value} at ${snap.timestamp}');
// Freeze — no further mutations allowed
score.freeze();
print(' Frozen: ${score.isFrozen}');
try {
score.value = 200;
} on FrozenReactiveException catch (e) {
print(' Caught: $e');
}
// Read-only view
final readOnly = score.readOnly;
print(' ReadOnly value: ${readOnly.value}');
score.dispose();
// Output: onDispose: score cleaned up
print('');
}
// =============================================================================
// Example 4 — Reactive collections
// =============================================================================
/// Demonstrates reactive List, Map, and Set.
void collectionsExample() {
print('=== Example 4: Reactive collections ===\n');
// ReactiveList
final items = ReactiveList<String>(items: ['alpha', 'beta'], name: 'items');
var changes = 0;
final sub = items.subscribe(() => changes++);
items.add('gamma');
items.batch((list) {
list.add('delta');
list.add('epsilon');
}); // single notification
print(' List: ${items.value}');
print(' List notifications: $changes');
// ReactiveMap
final config = ReactiveMap<String, int>(
entries: {'volume': 80, 'brightness': 70},
name: 'config',
);
config['contrast'] = 50;
print(' Map keys: ${config.keys.toList()}');
// ReactiveSet
final tags = ReactiveSet<String>(items: {'dart', 'flutter'}, name: 'tags');
tags.add('reactive');
print(' Set: ${tags.value}');
// Snapshot (immutable copy)
final snap = items.snapshot();
items.add('zeta');
print(' Snapshot (unchanged): $snap');
print(' Live list: ${items.value}');
sub.dispose();
items.dispose();
config.dispose();
tags.dispose();
print('');
}
// =============================================================================
// Example 5 — Async state management
// =============================================================================
/// Demonstrates AsyncReactive and Resource.
Future<void> asyncExample() async {
print('=== Example 5: Async state ===\n');
// Simulate an API call
Future<String> fetchGreeting() async {
await Future<void>.delayed(const Duration(milliseconds: 50));
return 'Hello from the server!';
}
final greeting = AsyncReactive<String>(fetchGreeting, name: 'greeting');
// State starts as loading
print(' Initial: ${greeting.state}');
// Wait for the fetch to complete
await Future<void>.delayed(const Duration(milliseconds: 100));
greeting.state.when(
loading: () => print(' Still loading...'),
data: (value) => print(' Data: $value'),
error: (e, _) => print(' Error: $e'),
);
// Resource with caching
var fetchCount = 0;
final resource = Resource<int>(
fetcher: () async {
fetchCount++;
await Future<void>.delayed(const Duration(milliseconds: 50));
return fetchCount * 10;
},
cacheDuration: const Duration(seconds: 5),
key: 'counter-resource',
name: 'counterResource',
);
await Future<void>.delayed(const Duration(milliseconds: 100));
print(' Resource: ${resource.state}');
print(' Is stale: ${resource.isStale}');
greeting.dispose();
resource.dispose();
print('');
}
// =============================================================================
// Example 6 — DI integration
// =============================================================================
/// Demonstrates deep integration with davianspace_dependencyinjection.
void diExample() {
print('=== Example 6: DI integration ===\n');
final services = ServiceCollection()
// Register a reactive singleton
..addReactiveSingleton<int>(() => Reactive<int>(0, name: 'counter'))
// Register a computed that depends on the reactive
..addComputedSingleton<String>((sp) {
final count = sp.getReactive<int>();
return Computed<String>(
() => 'Count is ${count.value}',
name: 'label',
);
})
// Register an effect
..addEffect((sp) {
final label = sp.getComputed<String>();
return Effect(
() => print(' Effect: ${label.value}'),
name: 'labelPrinter',
);
})
// Register a reactive list
..addReactiveListSingleton<String>(
() => ReactiveList<String>(items: ['item1', 'item2']),
);
final provider = services.buildServiceProvider();
// Resolve and use
final counter = provider.getReactive<int>();
final label = provider.getComputed<String>();
final items = provider.getRequired<ReactiveList<String>>();
print(' Counter: ${counter.value}');
print(' Label: ${label.value}');
print(' Items: ${items.value}');
// Update — effect re-runs automatically
counter.value = 42;
// Output: Effect: Count is 42
// Null-safe resolution
final missing = provider.tryGetReactive<double>();
print(' Missing: $missing'); // null
provider.dispose();
print('');
}
// =============================================================================
// Example 7 — Effects with debounce and throttle
// =============================================================================
/// Demonstrates debounced and throttled effects.
Future<void> effectOptionsExample() async {
print('=== Example 7: Debounce & throttle ===\n');
final searchQuery = Reactive<String>('', name: 'searchQuery');
final results = <String>[];
// Debounced effect — waits for quiet period before running
final debounced = Effect(
() {
final query = searchQuery.value;
if (query.isNotEmpty) {
results.add(query);
print(' Search: "$query"');
}
},
debounce: const Duration(milliseconds: 100),
fireImmediately: false,
name: 'searchEffect',
);
// Rapid updates — only the last one fires after the debounce window
searchQuery.value = 'D';
searchQuery.value = 'Da';
searchQuery.value = 'Dar';
searchQuery.value = 'Dart';
await Future<void>.delayed(const Duration(milliseconds: 200));
print(' Results: $results'); // ['Dart']
// watch() convenience
final counter = Reactive<int>(0, name: 'watchCounter');
final unsub = watch<int>(counter, (value) {
print(' Watched: $value');
});
counter.value = 1;
counter.value = 2;
unsub.dispose();
debounced.dispose();
searchQuery.dispose();
counter.dispose();
print('');
}
// =============================================================================
// Example 8 — Stream interop
// =============================================================================
/// Demonstrates bidirectional Stream ↔ Reactive conversion.
Future<void> streamExample() async {
print('=== Example 8: Stream interop ===\n');
// Stream → Reactive
final controller = StreamController<int>();
final fromStream = controller.stream.toReactive(0);
controller.add(10);
await Future<void>.delayed(Duration.zero);
print(' From stream: ${fromStream.peek()}');
// Reactive → Stream
final counter = Reactive<int>(0, name: 'streamCounter');
final stream = counter.asStream();
final collected = <int>[];
final subscription = stream.listen(collected.add);
counter.value = 1;
counter.value = 2;
counter.value = 3;
await Future<void>.delayed(Duration.zero);
print(' To stream: $collected');
await subscription.cancel();
await controller.close();
fromStream.dispose();
counter.dispose();
print('');
}
// =============================================================================
// Example 9 — Debug logging and graph inspection
// =============================================================================
/// Demonstrates devtools capabilities.
void devtoolsExample() {
print('=== Example 9: DevTools ===\n');
// Set up debug logger
final logger = ReactiveDebugLogger(maxEvents: 100);
ReactiveConfig.enableDebug(logger.onEvent);
// Create some reactives
final a = Reactive<int>(1, name: 'a');
final b = Reactive<int>(2, name: 'b');
final sum = Computed<int>(() => a.value + b.value, name: 'sum');
// Set up graph inspector
final inspector = GraphInspector.instance;
inspector.registerNode(a);
inspector.registerNode(b);
inspector.registerNode(sum);
// Use the reactives
print(' Sum: ${sum.value}');
a.value = 10;
print(' Sum: ${sum.value}');
// Query debug events
final writes = logger.eventsOfType('write');
print(' Write events: ${writes.length}');
// Check graph
final report = inspector.getPerformanceReport();
print(' Total nodes: ${report.totalNodes}');
print(' Total listeners: ${report.totalListeners}');
// Detect leaks
final leaks = inspector.detectLeaks();
print(' Potential leaks: ${leaks.length}');
// Clean up
logger.clear();
ReactiveConfig.disableDebug();
sum.dispose();
a.dispose();
b.dispose();
print('');
}
// =============================================================================
// Main
// =============================================================================
Future<void> main() async {
basicReactiveExample();
batchingExample();
lifecycleExample();
collectionsExample();
await asyncExample();
diExample();
await effectOptionsExample();
await streamExample();
devtoolsExample();
print('All examples completed.');
}