reactive_component 0.2.0 copy "reactive_component: ^0.2.0" to clipboard
reactive_component: ^0.2.0 copied to clipboard

A package that supports for creating stream-based reactive components.

ReactiveComponent #

A package that supports for creating stream-based reactive components, designed for simplicity, composability, flexibility.

With ReactiveComponent, it can compose models by taking advantage of Stream's powerful features in a concise codebase.

It is yet another state management package. It can be applied to any size of apps, from very small ones to large ones that usually have many complex component models.

When a ReactiveComponent wraps a domain model, it is described as implementing BLoC design pattern.

A quick example: counter model. #

/// [ReactiveComponent] is a unit that encapsulates its members
/// and publicizes only [Sink]s and [Stream]s.
class Counter with ReactiveComponent {
  Counter(this._initialCount);

  final int _initialCount;

  /// A special kind of [StreamSink] with its own single stream listener
  /// that handles the event data.
  VoidReactiveSink _increment;
  VoidReactiveSink get increment => _increment ??= VoidReactiveSink(() {
        // Increments _count on a increment event is delivered.
        _count.data++;
      }, disposer: disposer);

  /// A [Reactive] int data as count state of this counter.
  ///
  /// [Reactive] is a special kind of [StreamController] that holds its latest
  /// stream data, and sends that as the first data to any new listener.
  Reactive<int> __count;
  Reactive<int> get _count =>
      __count ??= Reactive<int>(_initialCount, disposer: disposer);

  /// Publicize only the stream of [_count] to hide its data mutating
  /// and the other behaviors.
  /// It's a good point to transform the stream as necessary.
  Stream<int> get count => _count.stream;
}

In current Dart spec, a lazy initialization technique with "??=" let them access the other instance members at its callback functions.

When "null-safety" is available in a future Dart, thankfully the notation will be conciser as below,

class Counter with ReactiveComponent {
  Counter(this._initialCount);

  final int _initialCount;

  late final VoidReactiveSink increment = VoidReactiveSink(() {
    _count.data++;
  }, disposer: disposer);

  late final _count = Reactive<int>(_initialCount, disposer: disposer);

  Stream<int> get count => _count.stream;
}

For a complete Flutter counter app example with more detailed comments, see Flutter counter example.

Examples #

There are two examples.

  • Flutter Counter
  • Composing Firebase

Motivation #

For simple app state management, ChangeNotifier and ValueNotifier are sufficient, although they are not suitable for fairly large apps that have many complex models.

There are the other options, including BLoC / Rx family that leverages Stream.

BLoC pattern has some advantages. By allowing only Sinks and Streams to its public interface, it can control its data flow in a unified way with Stream that is one of the core library of Dart. It has very rich capabilities to control data flow with various stream transformers. For instance, "throttle" (throttling data), "switchMap" (canceling ongoing data handling when a new data is comming), "exhaustMap" (discarding all incoming data while handling a data), "combineLatest" (combining each latest data from many streams), and many others.

Also, Sink and Stream I/O is a natural point for providing some hook methods . One major usage would be logging the I/O events for describing the data flow.

One disadvantage of implementing Sink and Stream I/O is that it can be cumbersome especially when implementing Sinks. Consequently, it is sometimes reasonable to loosen the constraint by not using Sinks, where a component loses its key capabilities on handling input data flow, which undermines composability.

Handling a model's state and the output stream is fortunately simple thanks to BehaviorSubject in RxDart. But it depends on RxDart anyway.

ReactiveComponent significantly reduces the boilerplate code of handling Sinks and Streams in a component model by its key features, while it still retains the flexibility to be partially composed of raw Streams as necessary ( e.g. when handling a Firestore's Stream ).

Key features #

ReactiveComponent #

class C with ReactiveComponent {}

ReactiveComponent is a unit for stream-based reactive programming, that encapsulates its members and publicizes only [Sink] and [Stream] interfaces.

Deliberately, it is designed as mixin, so that a subclass can freely inherit from other class as necessary.

It can be a delegate to its instance members of [ReactiveResource] via [disposer], for disposing of their resources together. [Reactive], [ReactiveSink], and [ReactiveComponent] itself are kind of [ReactiveResource].

For more explanations, see the API documentation.

Reactive #

Reactive is a special kind of [StreamController] that holds its latest stream data, and sends that as the first data to any new listener.

Reactive stream is a multi-subscription stream, added at Dart version 2.9.0. This allows multiple subscription, and each listener to be treated as an individual stream.

Reactive's [data] can get and set [data] synchronously. When a new [data] is set, it will be sent to all listeners of Reactive's stream immediately.

final aReactiveInt = Reactive<int>(0, disposer: null);

aReactiveInt.data = 1;
aReactiveInt.stream.listen(print); // prints 1 2 3.
aReactiveInt.data = 2;
aReactiveInt.stream.listen(print); // prints 2 3.
aReactiveInt.data = 3;
aReactiveInt.stream.listen(print); // prints 3.
print(aReactiveInt.data); // prints 3.

For more explanations, including "Disposing its resource" and "Reactive data should be encapsulated in a ReactiveComponent", see the API documentation.

ReactiveSink #

ReactiveSink is a special kind of [StreamSink] with its own single stream listener that handles the event data. Its event stream can be transformed by transform callback function passed at the constructor.

var i = 0;

final sink = ReactiveSink<int>((event) {
  i = i + event;
}, transform: (stream) => stream.map((e) => e * 2), disposer: null);

sink(1); // Shorthand notation of "sink.add(1);".

Future.microtask(() {
  print(i); // prints 2.
});

To save CPU and memory usage, the stream is lazily listened when a first data is added to the sink, or [HandleSubscription] is passed to its constructor.

For more explanations, including "Disposing its resource", see the API documentation.

Sub components #

ReactiveComponent can be nesting. The documentation will be added.

See Sub components pattern.

Platform agnostic #

ReactiveComponent is a platform-agnostic pure Dart package. It works in any platform, such as a Flutter app, an AngularDart app, a console app, or a Dart package.

Status #

The core features are almost stable. A few APIs will likely change. More features are planned, such as a hook API for both ReactiveSink and Reactive, some static analysis supports.

Features and bugs #

Please file feature requests and bugs at the issue tracker.

1
likes
30
pub points
11%
popularity

Publisher

verified publisherpolyflection.com

A package that supports for creating stream-based reactive components.

Repository (GitHub)
View/report issues

License

BSD-3-Clause (LICENSE)

Dependencies

meta, pedantic

More

Packages that depend on reactive_component