ValueStreams

ValueStreams

ValueStreams are a powerful concept in the Flood toolkit, built upon the foundation provided by the rxdart (opens in a new tab) package. While rxdart offers a wide range of utilities for handling streams, it lacks some essential features that Flood addresses. One of the key limitations of rxdart is that transforming a ValueStream always results in an asynchronous Stream instead of another ValueStream. Flood extends the functionality of ValueStream and BehaviorSubject from rxdart to provide a seamless way to transform ValueStreams into other ValueStreams, allowing you to retrieve the value of the stream synchronously.

FutureValues

Throughout the Flood codebase, you will frequently encounter ValueStream<FutureValue<T>>, where FutureValue represents the state of a Future, such as loading, loaded, or error. This concept is inspired by the freezed (opens in a new tab) package. A ValueStream<FutureValue<T>> essentially means a synchronous stream that tracks the status of a Future and how its value changes over time.

Naming Convention

In Flood, if a function or getter ends with an X, it indicates that it returns a ValueStream. For example, AuthService.static.memory().accountX returns a ValueStream of the currently logged-in Account. You can fetch this value synchronously by calling authService.accountX.value to know the current logged-in account, and you can also listen to or map the stream like you would with a regular Stream to make your app reactive.

Transforming ValueStreams

Flood provides a set of utility functions to transform ValueStreams while preserving their synchronous value access. Here are some of the notable utilities:

  1. mapWithValue: This method is similar to the standard map (opens in a new tab) operator, but it allows you to retrieve the current value of the transformed stream synchronously.

    final mappedStream = valueStream.mapWithValue((value) => value * 2);
    final currentValue = mappedStream.value;
  2. switchMapWithValue: This method is equivalent to the switchMap (opens in a new tab) operator, but it maintains the ability to retrieve the current value of the resulting stream synchronously.

    final switchedStream = valueStream.switchMapWithValue((value) => anotherValueStream);
    final currentValue = switchedStream.value;
  3. asyncMapWithValue: This method is similar to the asyncMap (opens in a new tab) operator, but it allows you to retrieve the latest value emitted by the stream. However, it's important to note that the async mapper only works when the stream is being listened to.

    final asyncMappedStream = valueStream.asyncMapWithValue((value) async => await someAsyncOperation(value));
    asyncMappedStream.listen((value) {
        final latestValue = asyncMappedStream.value;
    });

Combining ValueStreams

Flood also provides utilities to combine multiple ValueStreams into a single ValueStream while preserving the ability to access the combined value synchronously.

  • combineLatestWithValue: This method is similar to the combineLatest (opens in a new tab) operator, but it allows you to retrieve the current combined value synchronously.

    final combinedStream = [valueStream1, valueStream2].combineLatestWithValue((values) => values.reduce((a, b) => a + b));
    final currentCombinedValue = combinedStream.value;

Awaiting ValueStreams

Flood introduces the waitUntil method, which allows you to wait for a specific condition to be met on a ValueStream. It returns a Future that resolves when the provided predicate function returns true.

final result = await valueStream.waitUntil((value) => value > 10);

Converting Futures to ValueStreams

Flood provides the asValueStream extension method on Future, which converts a Future into a ValueStream that emits FutureValues representing the current state of the Future. This is particularly useful when working with asynchronous operations and wanting to handle their state reactively.

final futureValueStream = myFuture.asValueStream();
futureValueStream.listen((futureValue) {
    if (futureValue is FutureLoading) {
        // Handle loading state
    } else if (futureValue is FutureLoaded) {
        // Handle loaded state
    } else if (futureValue is FutureError) {
        // Handle error state
    }
});