davianspace_reactive 1.0.0 copy "davianspace_reactive: ^1.0.0" to clipboard
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/example.dart

// 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.');
}
0
likes
160
points
119
downloads

Documentation

API reference

Publisher

verified publisherdavian.space

Weekly Downloads

High-performance reactive state management for Dart and Flutter using signals, computed values, effects, batching, async state, and dependency injection.

Repository (GitHub)
View/report issues
Contributing

Topics

#state-management #reactive #signals #flutter #architecture

License

MIT (license)

Dependencies

davianspace_dependencyinjection, flutter

More

Packages that depend on davianspace_reactive