dart_observable 1.0.5 copy "dart_observable: ^1.0.5" to clipboard
dart_observable: ^1.0.5 copied to clipboard

A state management library using the Observer pattern, optimized for notifying changes in collections.

About #

Observable is a state management library that enables reactive programming,
based on the observer pattern without using streams.

Motivation #

Initial inspiration was taken from Kotlin StateFlow.
The library is designed to be simple, flexible, and efficient, but with explicit registration to avoid hidden dependencies.

The library can be used with any architecture.

With a strong focus on collections, it offers a way to track changes in sets, maps, or lists.
For collections see details below.

For the flutter library visit: fl_observable.

Features #

Mutable and Immutable Versions

  • The library offers both mutable and immutable versions of observables, including for collections.
  • The mutable version is called Rx, while the immutable version is called Observable.
  • You can expose the immutable version, while using the mutable version internally.
  • Immutable collection prevents direct modification, ensuring that changes are emitted through the observable.

Change Tracking:

  • Supports tracking changes in collections like sets, maps, or lists.
  • Operators and listeners can use the change to transform only the changed values, highly improving performance.

Factories and Operators:

  • The library includes factories and operators.

Observable #

  • The base class for all observables. Immutable version of the observable. Rx is the mutable version, which implements the Observable interface.
  • The observable can be listened to by multiple listeners.
Rx<int> _rxNumber = Rx<int>(1);
Observable<int> get rxNumber => _rxNumber; 
copied to clipboard

Factories #

just

Creates an observable with a single value.

final Observable<int> rxInt = Observable<int>.just(0);
copied to clipboard

combineLatest

Combines multiple observables into a single observable.
Anytime one of the observables changes, the combined observable will emit the new value.

final Rx<int> rxInt1 = Rx<int>(0);
final Rx<int> rxInt2 = Rx<int>(0);
final Rx<int> rxInt3 = Rx<int>(0);

final Observable<int> rxInt = Observable<int>.combineLatest(
  observables: <Observable<int>>[rxInt1, rxInt2, rxInt3],
  combiner: () {
    return rxInt1.value + rxInt2.value + rxInt3.value;
   },
);
copied to clipboard

fromFuture

Creates an observable from a future.

final Future<int> future = Future<int>.value(10);
final Observable<int> rxInt = Observable<int>.fromFuture(
    initial: 0,
    future: future,
);
copied to clipboard

fromStream

Creates an observable from a stream, listening to the stream and updating the observable.

final Stream<int> stream = Stream<int>.value(10);
final Observable<int> rxInt = Observable<int>.fromStream(
    initial: 0,
    stream: stream,
);
copied to clipboard

Operators #

map

Transforms the value of the observable.

Observable<int> rxSource = Observable<int>.just(0);
Observable<String> rxString = rxSource.map((value) => value.toString());
copied to clipboard

filter

Filters the value of the observable

Observable<int> rxSource = Observable<int>.just(0);
Observable<int> rxFiltered = rxSource.filter((value) => value % 2 == 0);
copied to clipboard

combineWith

Combines the observable with another observable.

final Rx<int> observable1 = Rx<int>(1);
final Rx<int> observable2 = Rx<int>(2);
final Observable<int> combined = observable1.combineWith<int, int>(
  other: observable2,
  combiner: (final int value1, final int value2) => value1 + value2,
);
copied to clipboard

handleError

Handles errors in the observable. Provides a way to recover from errors. Accept an optional predicate to filter the errors.

final Observable<int> rxInt = Observable<int>.just(0);
final Observable<int> rxIntHandled = rxInt.handleError(
  (final dynamic error, final Emitter<int> emit) {
    emit(1);
  },
  predicate: (final dynamic error) {
    return error is ArgumentError;
  });
copied to clipboard

transform

The most flexible operator that allows to transform the observable in any way. Emitter is used to emit any new value to the observable.
All other operators are built on top of this operator.

final Rx<int> rx = Rx<int>(0);
final Observable<double> transformed = rx.transform<double>(
  initialProvider: (final int value) {
    return value * 2.5;
  },
  onChanged: (
    final int value,
    final Emitter<double> emitter,
  ) {
    emitter(value * 2.5);
  },
);
copied to clipboard

transformAs

Used to transform the observable to a new collection, like a list, set, or map.

final Rx<String> rxSource = Rx<String>('Hello World');
final ObservableList<String> rxTransformed = rxSource.transformAs.list<String>(
  transform: (
    final ObservableList<String> state,
    final String value,
    final Emitter<List<String>> emitter,
  ) {
    emitter(<String>[for (final String char in value.split('')) char]);
  },
);
copied to clipboard

switchMap

On each change, switch to a new observable provided by the mapper function. Cancels the previous observable, and listens to the new observable.

final RxInt rxType1 = RxInt(1);
final RxInt rxType2 = RxInt(2);
final RxInt rxType3 = RxInt(3);
final RxList<int> rxSource = RxList<int>(<int>[1, 2, 3]);
final Observable<int> rxSwitched = rxSource.switchMap<int>(
  (final List<int> state) {
    final int mod = state.length % 3;
    if (mod == 0) {
      return rxType1;
    } else if (mod == 1) {
      return rxType2;
    } else {
      return rxType3;
    }
  },
);
copied to clipboard

switchMapAs

On each change, switch to a new collection provided by the mapper function.
Cancels the previous observable, and listens to the new observable.

final RxMap<int, String> rxType1 = RxMap<int, String>(<int, String>{1: '1'});
final RxMap<int, String> rxType2 = RxMap<int, String>(<int, String>{2: '2'});
final RxMap<int, String> rxType3 = RxMap<int, String>(<int, String>{3: '3'});
final Rx<int> rxSource = Rx<int>(0);
final ObservableMap<int, String> rxSwitched = rxSource.switchMapAs.map<int, String>(
  mapper: (final int value) {
    final int mod = value % 3;
    if (mod == 0) {
      return rxType1;
    } else if (mod == 1) {
      return rxType2;
    } else {
      return rxType3;
    }
  },
);
copied to clipboard

Collections #

Currently list, set and map are supported.
When the collection is modified, the observable will emit the change.
The change is used in the operators to transform only the changed values.

Example:
Adding a value to a list with 10.000 items will only emit the added value.
Listeners can use the change to transform that value.

In the following example, the initial list will be only transformed once, when the observable is listened. After that, only the change will be transformed and not the whole list.

RxList<int> rxLargeList = RxList<int>(List.generate(10000, (index) => index));
ObservableList<int> rxEvenItems = rxLargeList.filterItem((final int item) => item % 2 == 0);
ObservableList<int> rxEvenItemsMapped = rxEvenItems.mapItem((final int item) => item * 2);
rxLargeList.add(10000);
rxLargeList.removeAt(0);
copied to clipboard

Operators #

You can use the base operators, but those will always create a Observable and not a collection.
There are specific operators for collections that will return the same collection type.
These operators are optimized to transform only the changed values.

mapItem

Transforms each item in the collection.
The operator will emit only the changed items.
The operator is supported on sets, lists, and maps and their stateful versions.

final RxList<int> rxList = RxList<int>(<int>[1, 2, 3]);
final ObservableList<int> rxMapped = rxList.mapItem<int>((final int item) => item * 2);
copied to clipboard

filterItem

Filters the items in the collection.
The operator will emit only the changed items.
The operator is supported on sets, lists, and maps and their stateful versions.

final RxList<int> rxList = RxList<int>(<int>[1, 2, 3]);
final ObservableList<int> rxEvenItems = rxList.filterItem((final int item) => item % 2 == 0);
copied to clipboard

Stateful Collections #

Stateful collections support custom states, such as loading, error, or any user-defined state. Combines both the collection and the custom state, allowing the observable to represent either at any given time.
You can create any custom state you want.

This approach eliminates the need for multiple observables to manage different states.
For example, when fetching a list of items from a server, the observable can represent loading, error, or data states within a single unified state.
This state can be used in the listeners or in the downstream operators to handle the different states.

ObservableStatefulList<String, LoadingOrError> rxItems = getItemsFromServer();
rxItems.listen(onChange: (state) {
  state.when(
    onData: (ObservableListState<String> items) => , // Items loaded,
    onCustom: (LoadingOrError state) => , // Custom state,
  );
});
copied to clipboard
6
likes
140
points
54
downloads

Publisher

unverified uploader

Weekly Downloads

2024.09.08 - 2025.03.23

A state management library using the Observer pattern, optimized for notifying changes in collections.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

collection

More

Packages that depend on dart_observable