diff --git a/CHANGELOG.md b/CHANGELOG.md index 39cfb2a41..fec568aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.23.0-dev.1 + * Feedback on this change appreciated as this is a dev release before 0.23.0 stable! + * All transformation methods removed from Observable class + * Transformation methods are now Extensions of the Stream class + * Any Stream can make use of the transformation methods provided by RxDart + * Observable class remains in place with factory methods to create different types of Streams + * Removed deprecated `ofType` method, use `whereType` instead + * Deprecated `concatMap`, use standard Stream `asyncExpand`. + * Removed `AsObservableFuture`, `MinFuture`, `MaxFuture`, and `WrappedFuture` + * This removes `asObservable` method in chains + * Use default `asStream` method from the base `Future` class instead. + * `min` and `max` now implemented directly on the Stream class + ## 0.22.6 * Bugfix: When listening multiple times to a`BehaviorSubject` that starts with an Error, it emits duplicate events. diff --git a/README.md b/README.md index a8fd5c86c..bcf695487 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,20 @@ [![Gitter](https://img.shields.io/gitter/room/ReactiveX/rxdart.svg)](https://gitter.im/ReactiveX/rxdart) ## About -RxDart is a reactive functional programming library for Google Dart, based on [ReactiveX](http://reactivex.io/). -Google Dart comes with a very decent [Streams](https://api.dartlang.org/stable/1.21.1/dart-async/Stream-class.html) API out-of-the-box; rather than attempting to provide an alternative to this API, RxDart adds functionality on top of it. -## Version -Dart 1.0 is supported until release 0.15.x, -version 0.16.x is no longer backwards compatible and requires the Dart SDK 2.0 +RxDart is a library for working with Streams of data. + +Dart comes with a very decent [Streams](https://api.dart.dev/stable/dart-async/Stream-class.html) API out-of-the-box; rather than attempting to provide an alternative to this API, RxDart adds functionality from the reactive extensions specification on top of it. + +If you are familiar with Observables from other languages, please see [the Rx Observables vs Dart Streams comparison chart](#rx-observables-vs-dart-streams) for notable distinctions between the two. ## How To Use RxDart ### For Example: Reading the Konami Code ```dart +import 'package:rxdart/rxdart.dart'; + void main() { const konamiKeyCodes = const [ KeyCode.UP, @@ -29,14 +31,13 @@ void main() { KeyCode.LEFT, KeyCode.RIGHT, KeyCode.B, - KeyCode.A]; - + KeyCode.A, + ]; final result = querySelector('#result'); - final keyUp = Observable(document.onKeyUp); - keyUp + document.onKeyUp .map((event) => event.keyCode) - .bufferCount(10, 1) + .bufferCount(10, 1) // An extension method provided by rxdart .where((lastTenKeyCodes) => const IterableEquality().equals(lastTenKeyCodes, konamiKeyCodes)) .listen((_) => result.innerHtml = 'KONAMI!'); } @@ -44,73 +45,54 @@ void main() { ## API Overview -### Objects - -- [Observable](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable-class.html) -- [PublishSubject](https://www.dartdocs.org/documentation/rxdart/latest/rx/PublishSubject-class.html) -- [BehaviorSubject](https://www.dartdocs.org/documentation/rxdart/latest/rx/BehaviorSubject-class.html) -- [ReplaySubject](https://www.dartdocs.org/documentation/rxdart/latest/rx/ReplaySubject-class.html) +RxDart adds functionality to Dart Streams in two ways: -### Observable + * [Stream Classes](#stream-classes) - create Streams with specific capabilities, such as combining or merging many Streams together. + * Extension Methods - transform a source Stream into a new Stream with different capabilities, such as throttling events. -RxDart's Observables extends the Stream class. This has two major implications: -- All [methods defined on the Stream class](https://api.dartlang.org/stable/1.21.1/dart-async/Stream-class.html#instance-methods) exist on RxDart's Observables as well. -- All Observables can be passed to any API that expects a Dart Stream as an input. -- Additional important distinctions are documented as part of the [Observable class](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable-class.html) +### Stream Classes -Finally, the Observable class & operators are simple wrappers around `Stream` and `StreamTransformer` classes. All underlying implementations can be used free of the Observable class, and are exposed in their own libraries. They are linked to below. +The Stream class provides different ways to create a Stream: `Stream.fromIterable` or `Stream.periodic`, for example. RxDart provides additional Stream classes for a variety of tasks, such as combining or merging Streams together! -### Instantiation - -Generally speaking, creating a new Observable is either done by wrapping a Dart Stream using the top-level constructor `new Observable()`, or by calling a factory method on the Observable class. -But to better support Dart's strong mode, `combineLatest` and `zip` have been pulled apart into fixed-length constructors. -These methods are supplied as static methods, since Dart's factory methods don't support generic types. - -###### Usage -```dart -var myObservable = new Observable(myStream); -``` +You can construct the Streams provided by RxDart in two ways. The following examples are equivalent in terms of functionality: + + - Instantiating the Stream class directly. + - Example: `final myStream = MergeStream([myFirstStream, mySecondStream]);` + - Using the `Observable` class' factory constructors, which are useful for discovering which types of Streams are provided by RxDart. + - Example: `final myStream = Observable.merge([myFirstStream, mySecondStream]);` + +#### List of Classes / Helper Factories + +- [ConcatStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ConcatStream-class.html) / [Observable.concat](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.concat.html) +- [ConcatEagerStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ConcatEagerStream-class.html) / [Observable.concatEager](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.concatEager.html) +- [DeferStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/DeferStream-class.html) / [Observable.defer](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.defer.html) +- [Observable.just](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.just.html) +- [MergeStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/MergeStream-class.html) / [Observable.merge](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.merge.html) +- [NeverStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/NeverStream-class.html) / [Observable.never](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.never.html) +- [RaceStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RaceStream-class.html) / [Observable.race](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.race.html) +- [RepeatStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RepeatStream-class.html) / [Observable.repeat](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.repeat.html) +- [RetryStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RetryStream-class.html) / [Observable.retry](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.retry.html) +- [RetryWhenStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RetryWhenStream-class.html) / [Observable.retryWhen](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.retryWhen.html) +- [SequenceEqualStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/SequenceEqualStream-class.html) / [Observable.sequenceEqual](https://pub.dev/documentation/rxdart/latest/rx/Observable/sequenceEqual.html) +- [SwitchLatestStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/SwitchLatestStream-class.html) / [Observable.switchLatest](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.switchLatest.html) +- [TimerStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/TimerStream-class.html) / [Observable.timer](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.timer.html) +- [CombineLatestStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/CombineLatestStream-class.html) (combineLatest2, combineLatest... combineLatest9) / [Observable.combineLatest](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/combineLatest2.html) +- [ForkJoinStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ForkJoinStream-class.html) (forkJoin2, forkJoin... forkJoin9) / [Observable.forkJoin](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/forkJoin2.html) +- [RangeStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RangeStream-class.html) / [Observable.range](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/range.html) +- [ZipStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ZipStream-class.html) (zip2, zip3, zip4, ..., zip9) / [Observable.zip](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/zip2.html) + +### Extension Methods + +The extension methods provided by RxDart can be used on any `Stream`. They convert a source Stream into a new Stream with additional capabilities, such as buffering or throttling events. + +#### Example -#### Available Factory Methods -- [concat](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.concat.html) / [ConcatStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ConcatStream-class.html) -- [concatEager](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.concatEager.html) / [ConcatEagerStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ConcatEagerStream-class.html) -- [defer](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.defer.html) / [DeferStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/DeferStream-class.html) -- [error](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.error.html) / [ErrorStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ErrorStream-class.html) -- [just](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.just.html) -- [merge](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.merge.html) / [MergeStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/MergeStream-class.html) -- [never](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.never.html) / [NeverStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/NeverStream-class.html) -- [periodic](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.periodic.html) -- [race](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.race.html) / [RaceStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RaceStream-class.html) -- [repeat](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.repeat.html) / [RepeatStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RepeatStream-class.html) -- [retry](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.retry.html) / [RetryStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RetryStream-class.html) -- [retryWhen](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.retryWhen.html) / [RetryWhenStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RetryWhenStream-class.html) -- [sequenceEqual](https://pub.dev/documentation/rxdart/latest/rx/Observable/sequenceEqual.html) / [SequenceEqualStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/SequenceEqualStream-class.html) -- [switchLatest](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.switchLatest.html) / [SwitchLatestStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/SwitchLatestStream-class.html) -- [timer](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/Observable.timer.html) / [TimerStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/TimerStream-class.html) - -###### Usage ```dart -var myObservable = new Observable.merge([myFirstStream, mySecondStream]); -``` - -##### Available Static Methods -- [combineLatest](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/combineLatest2.html) / [CombineLatestStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/CombineLatestStream-class.html) (combineLatest2, combineLatest... combineLatest9) -- [forkJoin](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/forkJoin2.html) / [ForkJoinStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ForkJoinStream-class.html) (forkJoin2, forkJoin... forkJoin9) -- [range](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/range.html) / [RangeStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/RangeStream-class.html) -- [zip](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/zip2.html) / [ZipStream](https://www.dartdocs.org/documentation/rxdart/latest/rx/ZipStream-class.html) (zip2, zip3, zip4, ..., zip9) +final myStream = Stream.fromIterable([1, 2, 3]).debounceTime(Duration(seconds: 1)); +``` -###### Usage -```dart -var myObservable = Observable.combineLatest3( - myFirstStream, - mySecondStream, - myThirdStream, - (firstData, secondData, thirdData) => print("$firstData $secondData $thirdData")); -``` +#### List of Extension Methods -### Transformations - -##### Available Methods - [buffer](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/buffer.html) / [BufferStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/BufferStreamTransformer-class.html) - [bufferCount](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/bufferCount.html) / [BufferStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/BufferStreamTransformer-class.html) / [onCount](https://www.dartdocs.org/documentation/rxdart/latest/rx/onCount.html) - [bufferTest](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/bufferTest.html) / [BufferStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/BufferStreamTransformer-class.html) / [onTest](https://www.dartdocs.org/documentation/rxdart/latest/rx/onTest.html) @@ -140,6 +122,7 @@ var myObservable = Observable.combineLatest3( - [mergeWith](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/mergeWith.html) - [max](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/max.html) / [StreamMaxFuture](https://www.dartdocs.org/documentation/rxdart/latest/rx/StreamMaxFuture-class.html) - [min](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/min.html) / [StreamMinFuture](https://www.dartdocs.org/documentation/rxdart/latest/rx/StreamMinFuture-class.html) +- [onErrorClose](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/onErrorClose.html) - [onErrorResume](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/onErrorResume.html) / [OnErrorResumeStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/OnErrorResumeStreamTransformer-class.html) - [onErrorResumeNext](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/onErrorResumeNext.html) / [OnErrorResumeStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/OnErrorResumeStreamTransformer-class.html) - [onErrorReturn](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/onErrorReturn.html) / [OnErrorResumeStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/OnErrorResumeStreamTransformer-class.html) @@ -164,14 +147,23 @@ var myObservable = Observable.combineLatest3( - [withLatestFrom](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/withLatestFrom.html) / [WithLatestFromStreamTransformer](https://www.dartdocs.org/documentation/rxdart/latest/rx/WithLatestFromStreamTransformer-class.html) - [zipWith](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable/zipWith.html) -A full list of all methods and properties including those provided by the Dart Stream API (such as `first`, `asyncMap`, etc), can be seen by examining the [DartDocs](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable-class.html#instance-methods) +## Rx Observables vs Dart Streams -###### Usage -```Dart -var myObservable = new Observable(myStream) - .bufferCount(5) - .distinct(); -``` +In many situations, Streams and Observables work the same way. However, if you're used to standard Rx Observables, some features of the Stream api may surprise you. We've included a table below to help folks understand the differences. + +Additional information about the following situations can be found by reading the [Observable class documentation](https://www.dartdocs.org/documentation/rxdart/latest/rx/Observable-class.html). + +| Situation | Rx Observables | Dart Streams | +| ------------- |------------- | ------------- | +| An error is raised | Observable Terminates with Error | Error is emitted and Stream continues | +| Cold Observables | Multiple subscribers can listen to the same cold Observable, each subscription will receive a unique Stream of data | Single subscriber only | +| Hot Observables | Yes | Yes, known as Broadcast Streams | +| Is {Publish, Behavior, Replay}Subject hot? | Yes | Yes | +| Single/Maybe/Complete ? | Yes | No, uses Dart `Future` | +| Support back pressure| Yes | Yes | +| Can emit null? | Yes, except RxJava | Yes | +| Sync by default | Yes | No | +| Can pause/resume a subscription*? | No | Yes | ## Examples @@ -211,7 +203,8 @@ In order to run the flutter example, you must have Flutter installed. For instal 6. Run `flutter run` ## Notable References -- [Documentation on the Dart Stream class](https://api.dartlang.org/stable/2.0.0/dart-async/Stream-class.html) + +- [Documentation on the Dart Stream class](https://api.dart.dev/stable/dart-async/Stream-class.html) - [Tutorial on working with Streams in Dart](https://www.dartlang.org/tutorials/language/streams) - [ReactiveX (Rx)](http://reactivex.io/) diff --git a/lib/futures.dart b/lib/futures.dart deleted file mode 100644 index 49821f349..000000000 --- a/lib/futures.dart +++ /dev/null @@ -1,6 +0,0 @@ -library rx_futures; - -export 'package:rxdart/src/futures/as_observable_future.dart'; -export 'package:rxdart/src/futures/stream_max_future.dart'; -export 'package:rxdart/src/futures/stream_min_future.dart'; -export 'package:rxdart/src/futures/wrapped_future.dart'; diff --git a/lib/rxdart.dart b/lib/rxdart.dart index 73e033445..72ae08087 100644 --- a/lib/rxdart.dart +++ b/lib/rxdart.dart @@ -1,11 +1,10 @@ library rx; -export 'package:rxdart/futures.dart'; -export 'package:rxdart/src/observables/connectable_observable.dart'; -export 'package:rxdart/src/observables/observable.dart'; -export 'package:rxdart/src/observables/replay_observable.dart'; -export 'package:rxdart/src/observables/value_observable.dart'; -export 'package:rxdart/streams.dart'; -export 'package:rxdart/subjects.dart'; -export 'package:rxdart/transformers.dart'; -export 'package:rxdart/src/utils/composite_subscription.dart'; +export 'src/observables/connectable_observable.dart'; +export 'src/observables/observable.dart'; +export 'src/observables/replay_observable.dart'; +export 'src/observables/value_observable.dart'; +export 'src/utils/composite_subscription.dart'; +export 'streams.dart'; +export 'subjects.dart'; +export 'transformers.dart'; diff --git a/lib/src/futures/as_observable_future.dart b/lib/src/futures/as_observable_future.dart deleted file mode 100644 index 5ad42869e..000000000 --- a/lib/src/futures/as_observable_future.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/rxdart.dart'; -import 'package:rxdart/src/futures/wrapped_future.dart'; - -/// A future that can be converted directly to an Observable using -/// the `asObservable` method. -/// -/// This class simply wraps a normal Future, providing one additional method -/// for more fluent interoperability with the Observable class. -/// -/// Example: -/// -/// new Observable.fromIterable(["hello", "friends"]) -/// .join(" ") // Returns an AsObservableFuture -/// .asObservable() // Fluently convert the Future back to an Observable -/// .flatMap((message) => new Observable.just(message.length)); // Use the operators you need -class AsObservableFuture extends WrappedFuture { - /// Wraps a [Future] so that it can be exposed again as an [Observable]. - AsObservableFuture(Future wrapped) : super(wrapped); - - /// Returns an [Observable] containing the value from the wrapped [Future]. - Observable asObservable() => Observable.fromFuture(wrapped); -} diff --git a/lib/src/futures/stream_max_future.dart b/lib/src/futures/stream_max_future.dart deleted file mode 100644 index ecedd95c8..000000000 --- a/lib/src/futures/stream_max_future.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/src/futures/wrapped_future.dart'; - -/// Converts a Stream into a Future that completes with the largest item emitted -/// by the Stream. -/// -/// This is similar to finding the max value in a list, but the values are -/// asynchronous. -/// -/// ### Example -/// -/// int max = await new StreamMaxFuture(new Stream.fromIterable([1, 2, 3])); -/// -/// print(max); // prints 3 -/// -/// ### Example with custom [Comparator] -/// -/// Stream stream = new Stream.fromIterable(["short", "loooooooong"]); -/// Comparator stringLengthComparator = (a, b) => a.length - b.length; -/// String max = await new StreamMaxFuture(stream, stringLengthComparator); -/// -/// print(max); // prints "loooooooong" -class StreamMaxFuture extends WrappedFuture { - /// Constructs a [Future] which contains only the largest value emitted by a [Stream], - /// optionally using a [Comparator] to determine that value. - StreamMaxFuture(Stream stream, [Comparator comparator]) - : super(stream - .toList() - .then((List values) => (values..sort(comparator)).last)); -} diff --git a/lib/src/futures/stream_min_future.dart b/lib/src/futures/stream_min_future.dart deleted file mode 100644 index 90290a532..000000000 --- a/lib/src/futures/stream_min_future.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/src/futures/wrapped_future.dart'; - -/// Converts a Stream into a Future that completes with the smallest item -/// emitted by the Stream. -/// -/// This is similar to finding the min value in a list, but the values are -/// asynchronous. -/// -/// ### Example -/// -/// int min = await new StreamMinFuture(new Stream.fromIterable([1, 2, 3])); -/// -/// print(min); // prints 1 -/// -/// ### Example with custom [Comparator] -/// -/// Stream stream = new Stream.fromIterable("short", "loooooooong"); -/// Comparator stringLengthComparator = (a, b) => a.length - b.length; -/// String min = await new StreamMinFuture(stream, stringLengthComparator); -/// -/// print(min); // prints "short" -class StreamMinFuture extends WrappedFuture { - /// Constructs a [Future] which contains only the smallest value emitted by a [Stream], - /// optionally using a [Comparator] to determine that value. - StreamMinFuture(Stream stream, [Comparator comparator]) - : super(stream - .toList() - .then((List values) => (values..sort(comparator)).first)); -} diff --git a/lib/src/futures/wrapped_future.dart b/lib/src/futures/wrapped_future.dart deleted file mode 100644 index ce42f5809..000000000 --- a/lib/src/futures/wrapped_future.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:async'; - -/// A future that simply wraps another Future. -/// -/// This Future provides no additional functionality to the passed in Future -/// by default. This is meant as a base implementation that allows you to extend -/// Futures you can't directly create. -/// -/// For example, the AsObservableFuture adds one method to the Futures returned -/// by some Stream methods. -class WrappedFuture implements Future { - /// A reference to the wrapped [Future]. - final Future wrapped; - - /// Constructs a [Future] which wraps another [Future]. - WrappedFuture(this.wrapped); - - @override - Stream asStream() => wrapped.asStream(); - - @override - Future catchError(Function onError, {bool test(Object error)}) => - wrapped.catchError(onError, test: test); - - @override - Future then(FutureOr onValue(T value), {Function onError}) => - wrapped.then(onValue, onError: onError); - - @override - Future timeout(Duration timeLimit, {FutureOr onTimeout()}) => - wrapped.timeout(timeLimit, onTimeout: onTimeout); - - @override - Future whenComplete(void action()) => wrapped.whenComplete(action); -} diff --git a/lib/src/observables/connectable_observable.dart b/lib/src/observables/connectable_observable.dart index f8e3aaee8..120805f5c 100644 --- a/lib/src/observables/connectable_observable.dart +++ b/lib/src/observables/connectable_observable.dart @@ -273,3 +273,266 @@ class ConnectableObservableStreamSubscription extends StreamSubscription { @override void resume() => _source.resume(); } + +/// Extends the Stream class with the ability to transform a single-subscription +/// Stream into a ConnectableStream. +extension ConnectableObservableExtensions on Stream { + /// Convert the current Observable into a [ConnectableObservable] + /// that can be listened to multiple times. It will not begin emitting items + /// from the original Observable until the `connect` method is invoked. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream. + /// + /// ### Example + /// + /// ``` + /// final source = Observable.fromIterable([1, 2, 3]); + /// final connectable = source.publish(); + /// + /// // Does not print anything at first + /// connectable.listen(print); + /// + /// // Start listening to the source Observable. Will cause the previous + /// // line to start printing 1, 2, 3 + /// final subscription = connectable.connect(); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // Subject + /// subscription.cancel(); + /// ``` + ConnectableObservable publish() => PublishConnectableObservable(this); + + /// Convert the current Observable into a [ValueConnectableObservable] + /// that can be listened to multiple times. It will not begin emitting items + /// from the original Observable until the `connect` method is invoked. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream that replays the latest emitted value to any new + /// listener. It also provides access to the latest value synchronously. + /// + /// ### Example + /// + /// ``` + /// final source = Observable.fromIterable([1, 2, 3]); + /// final connectable = source.publishValue(); + /// + /// // Does not print anything at first + /// connectable.listen(print); + /// + /// // Start listening to the source Observable. Will cause the previous + /// // line to start printing 1, 2, 3 + /// final subscription = connectable.connect(); + /// + /// // Late subscribers will receive the last emitted value + /// connectable.listen(print); // Prints 3 + /// + /// // Can access the latest emitted value synchronously. Prints 3 + /// print(connectable.value); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // BehaviorSubject + /// subscription.cancel(); + /// ``` + ValueConnectableObservable publishValue() => + ValueConnectableObservable(this); + + /// Convert the current Observable into a [ValueConnectableObservable] + /// that can be listened to multiple times, providing an initial seeded value. + /// It will not begin emitting items from the original Observable + /// until the `connect` method is invoked. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream that replays the latest emitted value to any new + /// listener. It also provides access to the latest value synchronously. + /// + /// ### Example + /// + /// ``` + /// final source = Observable.fromIterable([1, 2, 3]); + /// final connectable = source.publishValueSeeded(0); + /// + /// // Does not print anything at first + /// connectable.listen(print); + /// + /// // Start listening to the source Observable. Will cause the previous + /// // line to start printing 0, 1, 2, 3 + /// final subscription = connectable.connect(); + /// + /// // Late subscribers will receive the last emitted value + /// connectable.listen(print); // Prints 3 + /// + /// // Can access the latest emitted value synchronously. Prints 3 + /// print(connectable.value); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // BehaviorSubject + /// subscription.cancel(); + /// ``` + ValueConnectableObservable publishValueSeeded(T seedValue) => + ValueConnectableObservable.seeded(this, seedValue); + + /// Convert the current Observable into a [ReplayConnectableObservable] + /// that can be listened to multiple times. It will not begin emitting items + /// from the original Observable until the `connect` method is invoked. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream that replays a given number of items to any new + /// listener. It also provides access to the emitted values synchronously. + /// + /// ### Example + /// + /// ``` + /// final source = Observable.fromIterable([1, 2, 3]); + /// final connectable = source.publishReplay(); + /// + /// // Does not print anything at first + /// connectable.listen(print); + /// + /// // Start listening to the source Observable. Will cause the previous + /// // line to start printing 1, 2, 3 + /// final subscription = connectable.connect(); + /// + /// // Late subscribers will receive the emitted value, up to a specified + /// // maxSize + /// connectable.listen(print); // Prints 1, 2, 3 + /// + /// // Can access a list of the emitted values synchronously. Prints [1, 2, 3] + /// print(connectable.values); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // ReplaySubject + /// subscription.cancel(); + /// ``` + ReplayConnectableObservable publishReplay({int maxSize}) => + ReplayConnectableObservable(this, maxSize: maxSize); + + /// Convert the current Observable into a new Observable that can be listened + /// to multiple times. It will automatically begin emitting items when first + /// listened to, and shut down when no listeners remain. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream. + /// + /// ### Example + /// + /// ``` + /// // Convert a single-subscription fromIterable stream into a broadcast + /// // stream + /// final observable = Observable.fromIterable([1, 2, 3]).share(); + /// + /// // Start listening to the source Observable. Will start printing 1, 2, 3 + /// final subscription = observable.listen(print); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // PublishSubject + /// subscription.cancel(); + /// ``` + Stream share() => publish().refCount(); + + /// Convert the current Observable into a new [ValueObservable] that can + /// be listened to multiple times. It will automatically begin emitting items + /// when first listened to, and shut down when no listeners remain. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream. It's also useful for providing sync access to the latest + /// emitted value. + /// + /// It will replay the latest emitted value to any new listener. + /// + /// ### Example + /// + /// ``` + /// // Convert a single-subscription fromIterable stream into a broadcast + /// // stream that will emit the latest value to any new listeners + /// final observable = Observable.fromIterable([1, 2, 3]).shareValue(); + /// + /// // Start listening to the source Observable. Will start printing 1, 2, 3 + /// final subscription = observable.listen(print); + /// + /// // Synchronously print the latest value + /// print(observable.value); + /// + /// // Subscribe again later. This will print 3 because it receives the last + /// // emitted value. + /// final subscription2 = observable.listen(print); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // BehaviorSubject by cancelling all subscriptions. + /// subscription.cancel(); + /// subscription2.cancel(); + /// ``` + ValueObservable shareValue() => publishValue().refCount(); + + /// Convert the current Observable into a new [ValueObservable] that can + /// be listened to multiple times, providing an initial value. + /// It will automatically begin emitting items when first listened to, + /// and shut down when no listeners remain. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream. It's also useful for providing sync access to the latest + /// emitted value. + /// + /// It will replay the latest emitted value to any new listener. + /// + /// ### Example + /// + /// ``` + /// // Convert a single-subscription fromIterable stream into a broadcast + /// // stream that will emit the latest value to any new listeners + /// final observable = Observable.fromIterable([1, 2, 3]).shareValueSeeded(0); + /// + /// // Start listening to the source Observable. Will start printing 0, 1, 2, 3 + /// final subscription = observable.listen(print); + /// + /// // Synchronously print the latest value + /// print(observable.value); + /// + /// // Subscribe again later. This will print 3 because it receives the last + /// // emitted value. + /// final subscription2 = observable.listen(print); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // BehaviorSubject by cancelling all subscriptions. + /// subscription.cancel(); + /// subscription2.cancel(); + /// ``` + ValueObservable shareValueSeeded(T seedValue) => + publishValueSeeded(seedValue).refCount(); + + /// Convert the current Observable into a new [ReplayObservable] that can + /// be listened to multiple times. It will automatically begin emitting items + /// when first listened to, and shut down when no listeners remain. + /// + /// This is useful for converting a single-subscription stream into a + /// broadcast Stream. It's also useful for gaining access to the l + /// + /// It will replay the emitted values to any new listener, up to a given + /// [maxSize]. + /// + /// ### Example + /// + /// ``` + /// // Convert a single-subscription fromIterable stream into a broadcast + /// // stream that will emit the latest value to any new listeners + /// final observable = Observable.fromIterable([1, 2, 3]).shareReplay(); + /// + /// // Start listening to the source Observable. Will start printing 1, 2, 3 + /// final subscription = observable.listen(print); + /// + /// // Synchronously print the emitted values up to a given maxSize + /// // Prints [1, 2, 3] + /// print(observable.values); + /// + /// // Subscribe again later. This will print 1, 2, 3 because it receives the + /// // last emitted value. + /// final subscription2 = observable.listen(print); + /// + /// // Stop emitting items from the source stream and close the underlying + /// // ReplaySubject by cancelling all subscriptions. + /// subscription.cancel(); + /// subscription2.cancel(); + /// ``` + ReplayObservable shareReplay({int maxSize}) => + publishReplay(maxSize: maxSize).refCount(); +} diff --git a/lib/src/observables/observable.dart b/lib/src/observables/observable.dart index b8a7eeab4..3d2a8842d 100644 --- a/lib/src/observables/observable.dart +++ b/lib/src/observables/observable.dart @@ -1,20 +1,15 @@ import 'dart:async'; -import 'package:rxdart/futures.dart'; -import 'package:rxdart/src/observables/connectable_observable.dart'; -import 'package:rxdart/src/observables/replay_observable.dart'; -import 'package:rxdart/src/observables/value_observable.dart'; import 'package:rxdart/streams.dart'; -import 'package:rxdart/transformers.dart'; /// A wrapper class that extends Stream. It combines all the Streams and /// StreamTransformers contained in this library into a fluent api. /// /// ### Example /// -/// new Observable(new Stream.fromIterable([1])) -/// .interval(new Duration(seconds: 1)) -/// .flatMap((i) => new Observable.just(2)) +/// Observable(Stream.fromIterable([1])) +/// .interval(Duration(seconds: 1)) +/// .flatMap((i) => Observable.just(2)) /// .take(1) /// .listen(print); // prints 2 /// @@ -81,10 +76,6 @@ class Observable extends Stream { /// invoked to transform the wrapped [Stream]. Observable(Stream stream) : this._stream = stream; - @override - AsObservableFuture any(bool test(T element)) => - AsObservableFuture(_stream.any(test)); - /// Merges the given Streams into one Observable sequence by using the /// [combiner] function whenever any of the observable sequences emits an item. /// This is helpful when you need to combine a dynamic number of Streams. @@ -97,8 +88,8 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest([ - /// new Observable.just("a"), - /// new Observable.fromIterable(["b", "c", "d"]) + /// Observable.just("a"), + /// Observable.fromIterable(["b", "c", "d"]) /// ], (list) => list.join()) /// .listen(print); // prints "ab", "ac", "ad" static Observable combineLatest( @@ -137,8 +128,8 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest2( - /// new Observable.just(1), - /// new Observable.fromIterable([0, 1, 2]), + /// Observable.just(1), + /// Observable.fromIterable([0, 1, 2]), /// (a, b) => a + b) /// .listen(print); //prints 1, 2, 3 static Observable combineLatest2( @@ -157,9 +148,9 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest3( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.fromIterable(["c", "c"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.fromIterable(["c", "c"]), /// (a, b, c) => a + b + c) /// .listen(print); //prints "abc", "abc" static Observable combineLatest3(Stream streamA, @@ -179,10 +170,10 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest4( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.fromIterable(["d", "d"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.fromIterable(["d", "d"]), /// (a, b, c, d) => a + b + c + d) /// .listen(print); //prints "abcd", "abcd" static Observable combineLatest4( @@ -206,11 +197,11 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest5( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.fromIterable(["e", "e"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.fromIterable(["e", "e"]), /// (a, b, c, d, e) => a + b + c + d + e) /// .listen(print); //prints "abcde", "abcde" static Observable combineLatest5( @@ -235,12 +226,12 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest6( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.fromIterable(["f", "f"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.fromIterable(["f", "f"]), /// (a, b, c, d, e, f) => a + b + c + d + e + f) /// .listen(print); //prints "abcdef", "abcdef" static Observable combineLatest6( @@ -266,13 +257,13 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest7( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.fromIterable(["g", "g"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.fromIterable(["g", "g"]), /// (a, b, c, d, e, f, g) => a + b + c + d + e + f + g) /// .listen(print); //prints "abcdefg", "abcdefg" static Observable combineLatest7( @@ -299,14 +290,14 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest8( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.just("g"), - /// new Observable.fromIterable(["h", "h"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.just("g"), + /// Observable.fromIterable(["h", "h"]), /// (a, b, c, d, e, f, g, h) => a + b + c + d + e + f + g + h) /// .listen(print); //prints "abcdefgh", "abcdefgh" static Observable combineLatest8( @@ -345,15 +336,15 @@ class Observable extends Stream { /// ### Example /// /// Observable.combineLatest9( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.just("g"), - /// new Observable.just("h"), - /// new Observable.fromIterable(["i", "i"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.just("g"), + /// Observable.just("h"), + /// Observable.fromIterable(["i", "i"]), /// (a, b, c, d, e, f, g, h, i) => a + b + c + d + e + f + g + h + i) /// .listen(print); //prints "abcdefghi", "abcdefghi" static Observable combineLatest9( @@ -392,10 +383,10 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.concat([ - /// new Observable.just(1), - /// new Observable.timer(2, new Duration(days: 1)), - /// new Observable.just(3) + /// Observable.concat([ + /// Observable.just(1), + /// Observable.timer(2, Duration(days: 1)), + /// Observable.just(3) /// ]) /// .listen(print); // prints 1, 2, 3 factory Observable.concat(Iterable> streams) => @@ -413,10 +404,10 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.concatEager([ - /// new Observable.just(1), - /// new Observable.timer(2, new Duration(days: 1)), - /// new Observable.just(3) + /// Observable.concatEager([ + /// Observable.just(1), + /// Observable.timer(2, Duration(days: 1)), + /// Observable.just(3) /// ]) /// .listen(print); // prints 1, 2, 3 factory Observable.concatEager(Iterable> streams) => @@ -434,7 +425,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.defer(() => new Observable.just(1)) + /// Observable.defer(() => Observable.just(1)) /// .listen(print); //prints 1 factory Observable.defer(Stream streamFactory(), {bool reusable = false}) => @@ -448,7 +439,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.error(new ArgumentError()); + /// Observable.error(ArgumentError()); factory Observable.error(Object error) => Observable(ErrorStream(error)); @@ -492,8 +483,8 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin([ - /// new Observable.just("a"), - /// new Observable.fromIterable(["b", "c", "d"]) + /// Observable.just("a"), + /// Observable.fromIterable(["b", "c", "d"]) /// ], (list) => list.join(', ')) /// .listen(print); // prints "a, d" static Observable forkJoin( @@ -521,8 +512,8 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin2( - /// new Observable.just(1), - /// new Observable.fromIterable([0, 1, 2]), + /// Observable.just(1), + /// Observable.fromIterable([0, 1, 2]), /// (a, b) => a + b) /// .listen(print); //prints 3 static Observable forkJoin2( @@ -536,9 +527,9 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin3( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.fromIterable(["c", "d"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.fromIterable(["c", "d"]), /// (a, b, c) => a + b + c) /// .listen(print); //prints "abd" static Observable forkJoin3(Stream streamA, @@ -553,10 +544,10 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin4( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.fromIterable(["d", "e"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.fromIterable(["d", "e"]), /// (a, b, c, d) => a + b + c + d) /// .listen(print); //prints "abce" static Observable forkJoin4( @@ -575,11 +566,11 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin5( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.fromIterable(["e", "f"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.fromIterable(["e", "f"]), /// (a, b, c, d, e) => a + b + c + d + e) /// .listen(print); //prints "abcdf" static Observable forkJoin5( @@ -599,12 +590,12 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin6( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.fromIterable(["f", "g"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.fromIterable(["f", "g"]), /// (a, b, c, d, e, f) => a + b + c + d + e + f) /// .listen(print); //prints "abcdeg" static Observable forkJoin6( @@ -625,13 +616,13 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin7( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.fromIterable(["g", "h"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.fromIterable(["g", "h"]), /// (a, b, c, d, e, f, g) => a + b + c + d + e + f + g) /// .listen(print); //prints "abcdefh" static Observable forkJoin7( @@ -653,14 +644,14 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin8( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.just("g"), - /// new Observable.fromIterable(["h", "i"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.just("g"), + /// Observable.fromIterable(["h", "i"]), /// (a, b, c, d, e, f, g, h) => a + b + c + d + e + f + g + h) /// .listen(print); //prints "abcdefgi" static Observable forkJoin8( @@ -694,15 +685,15 @@ class Observable extends Stream { /// ### Example /// /// Observable.forkJoin9( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.just("g"), - /// new Observable.just("h"), - /// new Observable.fromIterable(["i", "j"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.just("g"), + /// Observable.just("h"), + /// Observable.fromIterable(["i", "j"]), /// (a, b, c, d, e, f, g, h, i) => a + b + c + d + e + f + g + h + i) /// .listen(print); //prints "abcdefghj" static Observable forkJoin9( @@ -738,7 +729,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.fromFuture(new Future.value("Hello")) + /// Observable.fromFuture(Future.value("Hello")) /// .listen(print); // prints "Hello" factory Observable.fromFuture(Future future) => Observable((Stream.fromFuture(future))); @@ -755,7 +746,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.fromIterable([1, 2]).listen(print); // prints 1, 2 + /// Observable.fromIterable([1, 2]).listen(print); // prints 1, 2 factory Observable.fromIterable(Iterable data) => Observable((Stream.fromIterable(data))); @@ -765,7 +756,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.just(1).listen(print); // prints 1 + /// Observable.just(1).listen(print); // prints 1 factory Observable.just(T data) => Observable((Stream.fromIterable([data]))); @@ -775,7 +766,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.empty().listen( + /// Observable.empty().listen( /// (_) => print("data"), onDone: () => print("done")); // prints "done" factory Observable.empty() => Observable((Stream.empty())); @@ -786,9 +777,9 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.merge([ - /// new Observable.timer(1, new Duration(days: 10)), - /// new Observable.just(2) + /// Observable.merge([ + /// Observable.timer(1, Duration(days: 10)), + /// Observable.just(2) /// ]) /// .listen(print); // prints 2, 1 factory Observable.merge(Iterable> streams) => @@ -804,7 +795,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.never().listen(print); // Neither prints nor terminates + /// Observable.never().listen(print); // Neither prints nor terminates factory Observable.never() => Observable(NeverStream()); /// Creates an Observable that repeatedly emits events at [period] intervals. @@ -817,7 +808,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.periodic(new Duration(seconds: 1), (i) => i).take(3) + /// Observable.periodic(Duration(seconds: 1), (i) => i).take(3) /// .listen(print); // prints 0, 1, 2 factory Observable.periodic(Duration period, [T computation(int computationCount)]) => @@ -830,10 +821,10 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.race([ - /// new Observable.timer(1, new Duration(days: 1)), - /// new Observable.timer(2, new Duration(days: 2)), - /// new Observable.timer(3, new Duration(seconds: 1)) + /// Observable.race([ + /// Observable.timer(1, Duration(days: 1)), + /// Observable.timer(2, Duration(days: 2)), + /// Observable.timer(3, Duration(seconds: 1)) /// ]).listen(print); // prints 3 factory Observable.race(Iterable> streams) => Observable(RaceStream(streams)); @@ -857,7 +848,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new RepeatStream((int repeatCount) => + /// RepeatStream((int repeatCount) => /// Observable.just('repeat index: $repeatCount'), 3) /// .listen((i) => print(i)); // Prints 'repeat index: 0, repeat index: 1, repeat index: 2' factory Observable.repeat(Stream streamFactory(int repeatIndex), @@ -875,12 +866,12 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.retry(() { new Observable.just(1); }) + /// Observable.retry(() { Observable.just(1); }) /// .listen((i) => print(i)); // Prints 1 /// - /// new Observable + /// Observable /// .retry(() { - /// new Observable.just(1).concatWith([new Observable.error(new Error())]); + /// Observable.just(1).concatWith([Observable.error(Error())]); /// }, 1) /// .listen(print, onError: (e, s) => print(e)); // Prints 1, 1, RetryError factory Observable.retry(Stream streamFactory(), [int count]) { @@ -897,20 +888,20 @@ class Observable extends Stream { /// /// ### Basic Example /// ```dart - /// new RetryWhenStream( - /// () => new Stream.fromIterable([1]), + /// RetryWhenStream( + /// () => Stream.fromIterable([1]), /// (dynamic error, StackTrace s) => throw error, /// ).listen(print); // Prints 1 /// ``` /// /// ### Periodic Example /// ```dart - /// new RetryWhenStream( - /// () => new Observable + /// RetryWhenStream( + /// () => Observable /// .periodic(const Duration(seconds: 1), (int i) => i) /// .map((int i) => i == 2 ? throw 'exception' : i), /// (dynamic e, StackTrace s) { - /// return new Observable + /// return Observable /// .timer('random value', const Duration(milliseconds: 200)); /// }, /// ).take(4).listen(print); // Prints 0, 1, 0, 1 @@ -919,8 +910,8 @@ class Observable extends Stream { /// ### Complex Example /// ```dart /// bool errorHappened = false; - /// new RetryWhenStream( - /// () => new Observable + /// RetryWhenStream( + /// () => Observable /// .periodic(const Duration(seconds: 1), (i) => i) /// .map((i) { /// if (i == 3 && !errorHappened) { @@ -934,9 +925,9 @@ class Observable extends Stream { /// (e, s) { /// errorHappened = true; /// if (e == 'We can take this. Please restart.') { - /// return new Observable.just('Ok. Here you go!'); + /// return Observable.just('Ok. Here you go!'); /// } else { - /// return new Observable.error(e); + /// return Observable.error(e); /// } /// }, /// ).listen( @@ -975,11 +966,11 @@ class Observable extends Stream { /// ### Example /// /// ```dart - /// final switchLatestStream = new SwitchLatestStream( - /// new Stream.fromIterable(>[ - /// new Observable.timer('A', new Duration(seconds: 2)), - /// new Observable.timer('B', new Duration(seconds: 1)), - /// new Observable.just('C'), + /// final switchLatestStream = SwitchLatestStream( + /// Stream.fromIterable(>[ + /// Observable.timer('A', Duration(seconds: 2)), + /// Observable.timer('B', Duration(seconds: 1)), + /// Observable.just('C'), /// ]), /// ); /// @@ -995,7 +986,7 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.timer("hi", new Duration(minutes: 1)) + /// Observable.timer("hi", Duration(minutes: 1)) /// .listen((i) => print(i)); // print "hi" after 1 minute factory Observable.timer(T value, Duration duration) => Observable((TimerStream(value, duration))); @@ -1018,8 +1009,8 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip2( - /// new Observable.just("Hi "), - /// new Observable.fromIterable(["Friend", "Dropped"]), + /// Observable.just("Hi "), + /// Observable.fromIterable(["Friend", "Dropped"]), /// (a, b) => a + b) /// .listen(print); // prints "Hi Friend" static Observable zip2( @@ -1100,9 +1091,9 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip3( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.fromIterable(["c", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.fromIterable(["c", "dropped"]), /// (a, b, c) => a + b + c) /// .listen(print); //prints "abc" static Observable zip3(Stream streamA, Stream streamB, @@ -1127,10 +1118,10 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip4( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.fromIterable(["d", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.fromIterable(["d", "dropped"]), /// (a, b, c, d) => a + b + c + d) /// .listen(print); //prints "abcd" static Observable zip4(Stream streamA, Stream streamB, @@ -1155,11 +1146,11 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip5( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.fromIterable(["e", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.fromIterable(["e", "dropped"]), /// (a, b, c, d, e) => a + b + c + d + e) /// .listen(print); //prints "abcde" static Observable zip5( @@ -1190,12 +1181,12 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip6( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.fromIterable(["f", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.fromIterable(["f", "dropped"]), /// (a, b, c, d, e, f) => a + b + c + d + e + f) /// .listen(print); //prints "abcdef" static Observable zip6( @@ -1234,13 +1225,13 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip7( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.fromIterable(["g", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.fromIterable(["g", "dropped"]), /// (a, b, c, d, e, f, g) => a + b + c + d + e + f + g) /// .listen(print); //prints "abcdefg" static Observable zip7( @@ -1281,14 +1272,14 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip8( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.just("g"), - /// new Observable.fromIterable(["h", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.just("g"), + /// Observable.fromIterable(["h", "dropped"]), /// (a, b, c, d, e, f, g, h) => a + b + c + d + e + f + g + h) /// .listen(print); //prints "abcdefgh" static Observable zip8( @@ -1331,15 +1322,15 @@ class Observable extends Stream { /// ### Example /// /// Observable.zip9( - /// new Observable.just("a"), - /// new Observable.just("b"), - /// new Observable.just("c"), - /// new Observable.just("d"), - /// new Observable.just("e"), - /// new Observable.just("f"), - /// new Observable.just("g"), - /// new Observable.just("h"), - /// new Observable.fromIterable(["i", "dropped"]), + /// Observable.just("a"), + /// Observable.just("b"), + /// Observable.just("c"), + /// Observable.just("d"), + /// Observable.just("e"), + /// Observable.just("f"), + /// Observable.just("g"), + /// Observable.just("h"), + /// Observable.fromIterable(["i", "dropped"]), /// (a, b, c, d, e, f, g, h, i) => a + b + c + d + e + f + g + h + i) /// .listen(print); //prints "abcdefghi" static Observable zip9( @@ -1366,579 +1357,11 @@ class Observable extends Stream { zipper, )); - /// Returns a multi-subscription stream that produces the same events as this. - /// - /// The returned stream will subscribe to this stream when its first - /// subscriber is added, and will stay subscribed until this stream ends, or a - /// callback cancels the subscription. - /// - /// If onListen is provided, it is called with a subscription-like object that - /// represents the underlying subscription to this stream. It is possible to - /// pause, resume or cancel the subscription during the call to onListen. It - /// is not possible to change the event handlers, including using - /// StreamSubscription.asFuture. - /// - /// If onCancel is provided, it is called in a similar way to onListen when - /// the returned stream stops having listener. If it later gets a new - /// listener, the onListen function is called again. - /// - /// Use the callbacks, for example, for pausing the underlying subscription - /// while having no subscribers to prevent losing events, or canceling the - /// subscription when there are no listeners. - @override - Observable asBroadcastStream( - {void onListen(StreamSubscription subscription), - void onCancel(StreamSubscription subscription)}) => - Observable( - _stream.asBroadcastStream(onListen: onListen, onCancel: onCancel)); - - /// Maps each emitted item to a new [Stream] using the given mapper, then - /// subscribes to each new stream one after the next until all values are - /// emitted. - /// - /// asyncExpand is similar to flatMap, but ensures order by guaranteeing that - /// all items from the created stream will be emitted before moving to the - /// next created stream. This process continues until all created streams have - /// completed. - /// - /// This is functionally equivalent to `concatMap`, which exists as an alias - /// for a more fluent Rx API. - /// - /// ### Example - /// - /// Observable.range(4, 1) - /// .asyncExpand((i) => - /// new Observable.timer(i, new Duration(minutes: i)) - /// .listen(print); // prints 4, 3, 2, 1 - @override - Observable asyncExpand(Stream mapper(T value)) => - Observable(_stream.asyncExpand(mapper)); - - /// Creates an Observable with each data event of this stream asynchronously - /// mapped to a new event. - /// - /// This acts like map, except that convert may return a Future, and in that - /// case, the stream waits for that future to complete before continuing with - /// its result. - /// - /// The returned stream is a broadcast stream if this stream is. - @override - Observable asyncMap(FutureOr convert(T value)) => - Observable(_stream.asyncMap(convert)); - - /// Creates an Observable where each item is a [List] containing the items - /// from the source sequence. - /// - /// This [List] is emitted every time [window] emits an event. - /// - /// ### Example - /// - /// new Observable.periodic(const Duration(milliseconds: 100), (i) => i) - /// .buffer(new Stream.periodic(const Duration(milliseconds: 160), (i) => i)) - /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... - Observable> buffer(Stream window) => - transform(BufferStreamTransformer((_) => window)); - - /// Buffers a number of values from the source Observable by [count] then - /// emits the buffer and clears it, and starts a new buffer each - /// [startBufferEvery] values. If [startBufferEvery] is not provided, - /// then new buffers are started immediately at the start of the source - /// and when each buffer closes and is emitted. - /// - /// ### Example - /// [count] is the maximum size of the buffer emitted - /// - /// Observable.range(1, 4) - /// .bufferCount(2) - /// .listen(print); // prints [1, 2], [3, 4] done! - /// - /// ### Example - /// if [startBufferEvery] is 2, then a new buffer will be started - /// on every other value from the source. A new buffer is started at the - /// beginning of the source by default. - /// - /// Observable.range(1, 5) - /// .bufferCount(3, 2) - /// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! - Observable> bufferCount(int count, [int startBufferEvery = 0]) => - transform(BufferCountStreamTransformer(count, startBufferEvery)); - - /// Creates an Observable where each item is a [List] containing the items - /// from the source sequence, batched whenever test passes. - /// - /// ### Example - /// - /// new Observable.periodic(const Duration(milliseconds: 100), (int i) => i) - /// .bufferTest((i) => i % 2 == 0) - /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... - Observable> bufferTest(bool onTestHandler(T event)) => - transform(BufferTestStreamTransformer(onTestHandler)); - - /// Creates an Observable where each item is a [List] containing the items - /// from the source sequence, sampled on a time frame with [duration]. - /// - /// ### Example - /// - /// new Observable.periodic(const Duration(milliseconds: 100), (int i) => i) - /// .bufferTime(const Duration(milliseconds: 220)) - /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... - Observable> bufferTime(Duration duration) { - if (duration == null) throw ArgumentError.notNull('duration'); - - return buffer(Stream.periodic(duration)); - } - - /// - /// Adapt this stream to be a `Stream`. - /// - /// If this stream already has the desired type, its returned directly. - /// Otherwise it is wrapped as a `Stream` which checks at run-time that - /// each data event emitted by this stream is also an instance of [R]. - /// - @override - Observable cast() => Observable(_stream.cast()); - - /// Maps each emitted item to a new [Stream] using the given mapper, then - /// subscribes to each new stream one after the next until all values are - /// emitted. - /// - /// ConcatMap is similar to flatMap, but ensures order by guaranteeing that - /// all items from the created stream will be emitted before moving to the - /// next created stream. This process continues until all created streams have - /// completed. - /// - /// This is a simple alias for Dart Stream's `asyncExpand`, but is included to - /// ensure a more consistent Rx API. - /// - /// ### Example - /// - /// Observable.range(4, 1) - /// .concatMap((i) => - /// new Observable.timer(i, new Duration(minutes: i)) - /// .listen(print); // prints 4, 3, 2, 1 - Observable concatMap(Stream mapper(T value)) => - Observable(_stream.asyncExpand(mapper)); - - /// Returns an Observable that emits all items from the current Observable, - /// then emits all items from the given observable, one after the next. - /// - /// ### Example - /// - /// new Observable.timer(1, new Duration(seconds: 10)) - /// .concatWith([new Observable.just(2)]) - /// .listen(print); // prints 1, 2 - Observable concatWith(Iterable> other) => - Observable(ConcatStream(>[_stream]..addAll(other))); - - @override - AsObservableFuture contains(Object needle) => - AsObservableFuture(_stream.contains(needle)); - - /// Transforms a [Stream] so that will only emit items from the source sequence - /// if a [window] has completed, without the source sequence emitting - /// another item. - /// - /// This [window] is created after the last debounced event was emitted. - /// You can use the value of the last debounced event to determine - /// the length of the next [window]. - /// - /// A [window] is open until the first [window] event emits. - /// - /// debounce filters out items emitted by the source [Observable] - /// that are rapidly followed by another emitted item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#debounce) - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3, 4]) - /// .debounce((_) => TimerStream(true, const Duration(seconds: 1))) - /// .listen(print); // prints 4 - Observable debounce(Stream window(T event)) => - transform(DebounceStreamTransformer(window)); - - /// Transforms a [Stream] so that will only emit items from the source sequence - /// whenever the time span defined by [duration] passes, - /// without the source sequence emitting another item. - /// - /// This time span start after the last debounced event was emitted. - /// - /// debounceTime filters out items emitted by the source [Observable] - /// that are rapidly followed by another emitted item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#debounce) - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3, 4]) - /// .debounceTime(const Duration(seconds: 1)) - /// .listen(print); // prints 4 - Observable debounceTime(Duration duration) => transform( - DebounceStreamTransformer((_) => TimerStream(true, duration))); - - /// Emit items from the source Stream, or a single default item if the source - /// Stream emits nothing. - /// - /// ### Example - /// - /// new Observable.empty().defaultIfEmpty(10).listen(print); // prints 10 - Observable defaultIfEmpty(T defaultValue) => - transform(DefaultIfEmptyStreamTransformer(defaultValue)); - - /// The Delay operator modifies its source Observable by pausing for - /// a particular increment of time (that you specify) before emitting - /// each of the source Observable’s items. - /// This has the effect of shifting the entire sequence of items emitted - /// by the Observable forward in time by that specified increment. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#delay) - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3, 4]) - /// .delay(new Duration(seconds: 1)) - /// .listen(print); // [after one second delay] prints 1, 2, 3, 4 immediately - Observable delay(Duration duration) => - transform(DelayStreamTransformer(duration)); - - /// Converts the onData, onDone, and onError [Notification] objects from a - /// materialized stream into normal onData, onDone, and onError events. - /// - /// When a stream has been materialized, it emits onData, onDone, and onError - /// events as [Notification] objects. Dematerialize simply reverses this by - /// transforming [Notification] objects back to a normal stream of events. - /// - /// ### Example - /// - /// new Observable> - /// .fromIterable([new Notification.onData(1), new Notification.onDone()]) - /// .dematerialize() - /// .listen((i) => print(i)); // Prints 1 - /// - /// ### Error example - /// - /// new Observable> - /// .just(new Notification.onError(new Exception(), null)) - /// .dematerialize() - /// .listen(null, onError: (e, s) { print(e) }); // Prints Exception - Observable dematerialize() { - return cast>() - .transform(DematerializeStreamTransformer()); - } - - /// WARNING: More commonly known as distinctUntilChanged in other Rx - /// implementations. Creates an Observable where data events are skipped if - /// they are equal to the previous data event. - /// - /// The returned stream provides the same events as this stream, except that - /// it never provides two consecutive data events that are equal. - /// - /// Equality is determined by the provided equals method. If that is omitted, - /// the '==' operator on the last provided data element is used. - /// - /// The returned stream is a broadcast stream if this stream is. If a - /// broadcast stream is listened to more than once, each subscription will - /// individually perform the equals test. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#distinctUntilChanged) - @override - Observable distinct([bool equals(T previous, T next)]) => - Observable(_stream.distinct(equals)); - - /// WARNING: More commonly known as distinct in other Rx implementations. - /// Creates an Observable where data events are skipped if they have already - /// been emitted before. - /// - /// Equality is determined by the provided equals and hashCode methods. - /// If these are omitted, the '==' operator and hashCode on the last provided - /// data element are used. - /// - /// The returned stream is a broadcast stream if this stream is. If a - /// broadcast stream is listened to more than once, each subscription will - /// individually perform the equals and hashCode tests. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#distinct) - Observable distinctUnique({bool equals(T e1, T e2), int hashCode(T e)}) => - transform(DistinctUniqueStreamTransformer( - equals: equals, hashCode: hashCode)); - - /// Invokes the given callback function when the stream subscription is - /// cancelled. Often called doOnUnsubscribe or doOnDispose in other - /// implementations. - /// - /// ### Example - /// - /// final subscription = new Observable.timer(1, new Duration(minutes: 1)) - /// .doOnCancel(() => print("hi")); - /// .listen(null); - /// - /// subscription.cancel(); // prints "hi" - Observable doOnCancel(void onCancel()) => - transform(DoStreamTransformer(onCancel: onCancel)); - - /// Invokes the given callback function when the stream emits an item. In - /// other implementations, this is called doOnNext. - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3]) - /// .doOnData(print) - /// .listen(null); // prints 1, 2, 3 - Observable doOnData(void onData(T event)) => - transform(DoStreamTransformer(onData: onData)); - - /// Invokes the given callback function when the stream finishes emitting - /// items. In other implementations, this is called doOnComplete(d). - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3]) - /// .doOnDone(() => print("all set")) - /// .listen(null); // prints "all set" - Observable doOnDone(void onDone()) => - transform(DoStreamTransformer(onDone: onDone)); - - /// Invokes the given callback function when the stream emits data, emits - /// an error, or emits done. The callback receives a [Notification] object. - /// - /// The [Notification] object contains the [Kind] of event (OnData, onDone, - /// or OnError), and the item or error that was emitted. In the case of - /// onDone, no data is emitted as part of the [Notification]. - /// - /// ### Example - /// - /// new Observable.just(1) - /// .doOnEach(print) - /// .listen(null); // prints Notification{kind: OnData, value: 1, errorAndStackTrace: null}, Notification{kind: OnDone, value: null, errorAndStackTrace: null} - Observable doOnEach(void onEach(Notification notification)) => - transform(DoStreamTransformer(onEach: onEach)); - - /// Invokes the given callback function when the stream emits an error. - /// - /// ### Example - /// - /// new Observable.error(new Exception()) - /// .doOnError((error, stacktrace) => print("oh no")) - /// .listen(null); // prints "Oh no" - Observable doOnError(Function onError) => - transform(DoStreamTransformer(onError: onError)); - - /// Invokes the given callback function when the stream is first listened to. - /// - /// ### Example - /// - /// new Observable.just(1) - /// .doOnListen(() => print("Is someone there?")) - /// .listen(null); // prints "Is someone there?" - Observable doOnListen(void onListen()) => - transform(DoStreamTransformer(onListen: onListen)); - - /// Invokes the given callback function when the stream subscription is - /// paused. - /// - /// ### Example - /// - /// final subscription = new Observable.just(1) - /// .doOnPause(() => print("Gimme a minute please")) - /// .listen(null); - /// - /// subscription.pause(); // prints "Gimme a minute please" - Observable doOnPause(void onPause(Future resumeSignal)) => - transform(DoStreamTransformer(onPause: onPause)); - - /// Invokes the given callback function when the stream subscription - /// resumes receiving items. - /// - /// ### Example - /// - /// final subscription = new Observable.just(1) - /// .doOnResume(() => print("Let's do this!")) - /// .listen(null); - /// - /// subscription.pause(); - /// subscription.resume(); "Let's do this!" - Observable doOnResume(void onResume()) => - transform(DoStreamTransformer(onResume: onResume)); - - @override - AsObservableFuture drain([S futureValue]) => - AsObservableFuture(_stream.drain(futureValue)); - - @override - AsObservableFuture elementAt(int index) => - AsObservableFuture(_stream.elementAt(index)); - - @override - AsObservableFuture every(bool test(T element)) => - AsObservableFuture(_stream.every(test)); - - /// Converts items from the source stream into a new Stream using a given - /// mapper. It ignores all items from the source stream until the new stream - /// completes. - /// - /// Useful when you have a noisy source Stream and only want to respond once - /// the previous async operation is finished. - /// - /// ### Example - /// - /// Observable.range(0, 2).interval(new Duration(milliseconds: 50)) - /// .exhaustMap((i) => - /// new Observable.timer(i, new Duration(milliseconds: 75))) - /// .listen(print); // prints 0, 2 - Observable exhaustMap(Stream mapper(T value)) => - transform(ExhaustMapStreamTransformer(mapper)); - - /// Creates an Observable from this stream that converts each element into - /// zero or more events. - /// - /// Each incoming event is converted to an Iterable of new events, and each of - /// these new events are then sent by the returned Observable in order. - /// - /// The returned Observable is a broadcast stream if this stream is. If a - /// broadcast stream is listened to more than once, each subscription will - /// individually call convert and expand the events. - @override - Observable expand(Iterable convert(T value)) => - Observable(_stream.expand(convert)); - - @override - AsObservableFuture get first => AsObservableFuture(_stream.first); - - @override - AsObservableFuture firstWhere(bool test(T element), - {dynamic defaultValue(), T orElse()}) => - AsObservableFuture(_stream.firstWhere(test, orElse: orElse)); - - /// Converts each emitted item into a new Stream using the given mapper - /// function. The newly created Stream will be be listened to and begin - /// emitting items downstream. - /// - /// The items emitted by each of the new Streams are emitted downstream in the - /// same order they arrive. In other words, the sequences are merged - /// together. - /// - /// ### Example - /// - /// Observable.range(4, 1) - /// .flatMap((i) => - /// new Observable.timer(i, new Duration(minutes: i)) - /// .listen(print); // prints 1, 2, 3, 4 - Observable flatMap(Stream mapper(T value)) => - transform(FlatMapStreamTransformer(mapper)); - - /// Converts each item into a new Stream. The Stream must return an - /// Iterable. Then, each item from the Iterable will be emitted one by one. - /// - /// Use case: you may have an API that returns a list of items, such as - /// a Stream>. However, you might want to operate on the individual items - /// rather than the list itself. This is the job of `flatMapIterable`. - /// - /// ### Example - /// - /// Observable.range(1, 4) - /// .flatMapIterable((i) => - /// new Observable.just([i]) - /// .listen(print); // prints 1, 2, 3, 4 - Observable flatMapIterable(Stream> mapper(T value)) => - transform(FlatMapStreamTransformer>(mapper)) - .expand((Iterable iterable) => iterable); - - @override - AsObservableFuture fold( - S initialValue, S combine(S previous, T element)) => - AsObservableFuture(_stream.fold(initialValue, combine)); - - @override - AsObservableFuture forEach(void action(T element)) => - AsObservableFuture(_stream.forEach(action)); - - /// The GroupBy operator divides an [Observable] that emits items into - /// an [Observable] that emits [GroupByObservable], - /// each one of which emits some subset of the items - /// from the original source [Observable]. - /// - /// [GroupByObservable] acts like a regular [Observable], yet - /// adding a 'key' property, which receives its [Type] and value from - /// the [grouper] Function. - /// - /// All items with the same key are emitted by the same [GroupByObservable]. - Observable> groupBy(S grouper(T value)) => - transform(GroupByStreamTransformer(grouper)); - - /// Creates a wrapper Stream that intercepts some errors from this stream. - /// - /// If this stream sends an error that matches test, then it is intercepted by - /// the handle function. - /// - /// The onError callback must be of type void onError(error) or void - /// onError(error, StackTrace stackTrace). Depending on the function type the - /// stream either invokes onError with or without a stack trace. The stack - /// trace argument might be null if the stream itself received an error - /// without stack trace. - /// - /// An asynchronous error e is matched by a test function if test(e) returns - /// true. If test is omitted, every error is considered matching. - /// - /// If the error is intercepted, the handle function can decide what to do - /// with it. It can throw if it wants to raise a new (or the same) error, or - /// simply return to make the stream forget the error. - /// - /// If you need to transform an error into a data event, use the more generic - /// Stream.transform to handle the event by writing a data event to the output - /// sink. - /// - /// The returned stream is a broadcast stream if this stream is. If a - /// broadcast stream is listened to more than once, each subscription will - /// individually perform the test and handle the error. - @override - Observable handleError(Function onError, {bool test(dynamic error)}) => - Observable(_stream.handleError(onError, test: test)); - - /// Creates an Observable where all emitted items are ignored, only the - /// error / completed notifications are passed - /// - /// ### Example - /// - /// new Observable.merge([ - /// new Observable.just(1), - /// new Observable.error(new Exception()) - /// ]) - /// .listen(print, onError: print); // prints Exception - Observable ignoreElements() => - transform(IgnoreElementsStreamTransformer()); - - /// Creates an Observable that emits each item in the Stream after a given - /// duration. - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3]) - /// .interval(new Duration(seconds: 1)) - /// .listen((i) => print("$i sec"); // prints 1 sec, 2 sec, 3 sec - Observable interval(Duration duration) => - transform(IntervalStreamTransformer(duration)); - @override bool get isBroadcast { return (_stream != null) ? _stream.isBroadcast : false; } - @override - AsObservableFuture get isEmpty => - AsObservableFuture(_stream.isEmpty); - - @override - AsObservableFuture join([String separator = ""]) => - AsObservableFuture(_stream.join(separator)); - - @override - AsObservableFuture get last => AsObservableFuture(_stream.last); - - @override - AsObservableFuture lastWhere(bool test(T element), - {Object defaultValue(), T orElse()}) => - AsObservableFuture(_stream.lastWhere(test, orElse: orElse)); - /// Adds a subscription to this stream. Returns a [StreamSubscription] which /// handles events from the stream using the provided [onData], [onError] and /// [onDone] handlers. @@ -1975,1219 +1398,11 @@ class Observable extends Stream { /// /// ### Example /// - /// new Observable.just(1).listen(print); // prints 1 + /// Observable.just(1).listen(print); // prints 1 @override StreamSubscription listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError}) { return _stream.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); } - - @override - AsObservableFuture get length => AsObservableFuture(_stream.length); - - /// Maps values from a source sequence through a function and emits the - /// returned values. - /// - /// The returned sequence completes when the source sequence completes. - /// The returned sequence throws an error if the source sequence throws an - /// error. - @override - Observable map(S convert(T event)) => - Observable(_stream.map(convert)); - - /// Emits the given constant value on the output Observable every time the source Observable emits a value. - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2, 3, 4]) - /// .mapTo(true) - /// .listen(print); // prints true, true, true, true - Observable mapTo(S value) => - transform(MapToStreamTransformer(value)); - - /// Converts the onData, on Done, and onError events into [Notification] - /// objects that are passed into the downstream onData listener. - /// - /// The [Notification] object contains the [Kind] of event (OnData, onDone, or - /// OnError), and the item or error that was emitted. In the case of onDone, - /// no data is emitted as part of the [Notification]. - /// - /// Example: - /// new Observable.just(1) - /// .materialize() - /// .listen((i) => print(i)); // Prints onData & onDone Notification - /// - /// new Observable.error(new Exception()) - /// .materialize() - /// .listen((i) => print(i)); // Prints onError Notification - Observable> materialize() => - transform(MaterializeStreamTransformer()); - - /// Converts a Stream into a Future that completes with the largest item emitted - /// by the Stream. - /// - /// This is similar to finding the max value in a list, but the values are - /// asynchronous. - /// - /// ### Example - /// - /// final max = await new Observable.fromIterable([1, 2, 3]).max(); - /// - /// print(max); // prints 3 - /// - /// ### Example with custom [Comparator] - /// - /// final observable = new Observable.fromIterable(["short", "looooooong"]); - /// final max = await observable.max((a, b) => a.length - b.length); - /// - /// print(max); // prints "looooooong" - AsObservableFuture max([Comparator comparator]) => - AsObservableFuture(StreamMaxFuture(_stream, comparator)); - - /// Combines the items emitted by multiple streams into a single stream of - /// items. The items are emitted in the order they are emitted by their - /// sources. - /// - /// ### Example - /// - /// new Observable.timer(1, new Duration(seconds: 10)) - /// .mergeWith([new Observable.just(2)]) - /// .listen(print); // prints 2, 1 - Observable mergeWith(Iterable> streams) => - Observable(MergeStream(>[_stream]..addAll(streams))); - - /// Converts a Stream into a Future that completes with the smallest item - /// emitted by the Stream. - /// - /// This is similar to finding the min value in a list, but the values are - /// asynchronous! - /// - /// ### Example - /// - /// final min = await new Observable.fromIterable([1, 2, 3]).min(); - /// - /// print(min); // prints 1 - /// - /// ### Example with custom [Comparator] - /// - /// final observable = new Observable.fromIterable(["short", "looooooong"]); - /// final min = await observable.min((a, b) => a.length - b.length); - /// - /// print(min); // prints "short" - AsObservableFuture min([Comparator comparator]) => - AsObservableFuture(StreamMinFuture(_stream, comparator)); - - /// Filters a sequence so that only events of a given type pass - /// - /// In order to capture the Type correctly, it needs to be wrapped - /// in a [TypeToken] as the generic parameter. - /// - /// Given the way Dart generics work, one cannot simply use the `is T` / `as T` - /// checks and castings with this method alone. Therefore, the - /// [TypeToken] class was introduced to capture the type of class you'd - /// like `ofType` to filter down to. - /// - /// ### Examples - /// - /// new Observable.fromIterable([1, "hi"]) - /// .ofType(new TypeToken) - /// .listen(print); // prints "hi" - /// - /// As a shortcut, you can use some pre-defined constants to write the above - /// in the following way: - /// - /// new Observable.fromIterable([1, "hi"]) - /// .ofType(kString) - /// .listen(print); // prints "hi" - /// - /// If you'd like to create your own shortcuts like the example above, - /// simply create a constant: - /// - /// const TypeToken> kMapIntString = - /// const TypeToken>(); - @Deprecated('Please use whereType instead') - Observable ofType(TypeToken typeToken) => - transform(OfTypeStreamTransformer(typeToken)); - - /// Intercepts error events and switches to the given recovery stream in - /// that case - /// - /// The onErrorResumeNext operator intercepts an onError notification from - /// the source Observable. Instead of passing the error through to any - /// listeners, it replaces it with another Stream of items. - /// - /// If you need to perform logic based on the type of error that was emitted, - /// please consider using [onErrorResume]. - /// - /// ### Example - /// - /// new Observable.error(new Exception()) - /// .onErrorResumeNext(new Observable.fromIterable([1, 2, 3])) - /// .listen(print); // prints 1, 2, 3 - Observable onErrorResumeNext(Stream recoveryStream) => transform( - OnErrorResumeStreamTransformer((dynamic e) => recoveryStream)); - - /// Intercepts error events and switches to a recovery stream created by the - /// provided [recoveryFn]. - /// - /// The onErrorResume operator intercepts an onError notification from - /// the source Observable. Instead of passing the error through to any - /// listeners, it replaces it with another Stream of items created by the - /// [recoveryFn]. - /// - /// The [recoveryFn] receives the emitted error and returns a Stream. You can - /// perform logic in the [recoveryFn] to return different Streams based on the - /// type of error that was emitted. - /// - /// If you do not need to perform logic based on the type of error that was - /// emitted, please consider using [onErrorResumeNext] or [onErrorReturn]. - /// - /// ### Example - /// - /// new Observable.error(new Exception()) - /// .onErrorResume((dynamic e) => - /// new Observable.just(e is StateError ? 1 : 0) - /// .listen(print); // prints 0 - Observable onErrorResume(Stream Function(dynamic error) recoveryFn) => - transform(OnErrorResumeStreamTransformer(recoveryFn)); - - /// instructs an Observable to emit a particular item when it encounters an - /// error, and then terminate normally - /// - /// The onErrorReturn operator intercepts an onError notification from - /// the source Observable. Instead of passing it through to any observers, it - /// replaces it with a given item, and then terminates normally. - /// - /// If you need to perform logic based on the type of error that was emitted, - /// please consider using [onErrorReturnWith]. - /// - /// ### Example - /// - /// new Observable.error(new Exception()) - /// .onErrorReturn(1) - /// .listen(print); // prints 1 - Observable onErrorReturn(T returnValue) => - transform(OnErrorResumeStreamTransformer( - (dynamic e) => Observable.just(returnValue))); - - /// instructs an Observable to emit a particular item created by the - /// [returnFn] when it encounters an error, and then terminate normally. - /// - /// The onErrorReturnWith operator intercepts an onError notification from - /// the source Observable. Instead of passing it through to any observers, it - /// replaces it with a given item, and then terminates normally. - /// - /// The [returnFn] receives the emitted error and returns a Stream. You can - /// perform logic in the [returnFn] to return different Streams based on the - /// type of error that was emitted. - /// - /// If you do not need to perform logic based on the type of error that was - /// emitted, please consider using [onErrorReturn]. - /// - /// ### Example - /// - /// new Observable.error(new Exception()) - /// .onErrorReturnWith((e) => e is Exception ? 1 : 0) - /// .listen(print); // prints 1 - Observable onErrorReturnWith(T Function(dynamic error) returnFn) => - transform(OnErrorResumeStreamTransformer( - (dynamic e) => Observable.just(returnFn(e)))); - - /// Emits the n-th and n-1th events as a pair.. - /// - /// ### Example - /// - /// Observable.range(1, 4) - /// .pairwise() - /// .listen(print); // prints [1, 2], [2, 3], [3, 4] - Observable> pairwise() => transform(PairwiseStreamTransformer()); - - @override - AsObservableFuture pipe(StreamConsumer streamConsumer) => - AsObservableFuture(_stream.pipe(streamConsumer)); - - @override - AsObservableFuture reduce(T combine(T previous, T element)) => - AsObservableFuture(_stream.reduce(combine)); - - /// Emits the most recently emitted item (if any) - /// emitted by the source [Stream] since the previous emission from - /// the [sampleStream]. - /// - /// ### Example - /// - /// new Stream.fromIterable([1, 2, 3]) - /// .sample(new TimerStream(1, const Duration(seconds: 1))) - /// .listen(print); // prints 3 - Observable sample(Stream sampleStream) => - transform(SampleStreamTransformer((_) => sampleStream)); - - /// Emits the most recently emitted item (if any) - /// emitted by the source [Stream] since the previous emission within - /// the recurring time span, defined by [duration] - /// - /// ### Example - /// - /// new Stream.fromIterable([1, 2, 3]) - /// .sampleTime(const Duration(seconds: 1)) - /// .listen(print); // prints 3 - Observable sampleTime(Duration duration) => - sample(Stream.periodic(duration)); - - /// Applies an accumulator function over an observable sequence and returns - /// each intermediate result. The optional seed value is used as the initial - /// accumulator value. - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2, 3]) - /// .scan((acc, curr, i) => acc + curr, 0) - /// .listen(print); // prints 1, 3, 6 - Observable scan(S accumulator(S accumulated, T value, int index), - [S seed]) => - transform(ScanStreamTransformer(accumulator, seed)); - - @override - AsObservableFuture get single => AsObservableFuture(_stream.single); - - @override - AsObservableFuture singleWhere(bool test(T element), {T orElse()}) => - AsObservableFuture(_stream.singleWhere(test, orElse: orElse)); - - /// Skips the first count data events from this stream. - /// - /// The returned stream is a broadcast stream if this stream is. For a - /// broadcast stream, the events are only counted from the time the returned - /// stream is listened to. - @override - Observable skip(int count) => Observable(_stream.skip(count)); - - /// Starts emitting items only after the given stream emits an item. - /// - /// ### Example - /// - /// new Observable.merge([ - /// new Observable.just(1), - /// new Observable.timer(2, new Duration(minutes: 2)) - /// ]) - /// .skipUntil(new Observable.timer(true, new Duration(minutes: 1))) - /// .listen(print); // prints 2; - Observable skipUntil(Stream otherStream) => - transform(SkipUntilStreamTransformer(otherStream)); - - /// Skip data events from this stream while they are matched by test. - /// - /// Error and done events are provided by the returned stream unmodified. - /// - /// Starting with the first data event where test returns false for the event - /// data, the returned stream will have the same events as this stream. - /// - /// The returned stream is a broadcast stream if this stream is. For a - /// broadcast stream, the events are only tested from the time the returned - /// stream is listened to. - @override - Observable skipWhile(bool test(T element)) => - Observable(_stream.skipWhile(test)); - - /// Prepends a value to the source Observable. - /// - /// ### Example - /// - /// new Observable.just(2).startWith(1).listen(print); // prints 1, 2 - Observable startWith(T startValue) => - transform(StartWithStreamTransformer(startValue)); - - /// Prepends a sequence of values to the source Observable. - /// - /// ### Example - /// - /// new Observable.just(3).startWithMany([1, 2]) - /// .listen(print); // prints 1, 2, 3 - Observable startWithMany(List startValues) => - transform(StartWithManyStreamTransformer(startValues)); - - /// When the original observable emits no items, this operator subscribes to - /// the given fallback stream and emits items from that observable instead. - /// - /// This can be particularly useful when consuming data from multiple sources. - /// For example, when using the Repository Pattern. Assuming you have some - /// data you need to load, you might want to start with the fastest access - /// point and keep falling back to the slowest point. For example, first query - /// an in-memory database, then a database on the file system, then a network - /// call if the data isn't on the local machine. - /// - /// This can be achieved quite simply with switchIfEmpty! - /// - /// ### Example - /// - /// // Let's pretend we have some Data sources that complete without emitting - /// // any items if they don't contain the data we're looking for - /// Observable memory; - /// Observable disk; - /// Observable network; - /// - /// // Start with memory, fallback to disk, then fallback to network. - /// // Simple as that! - /// Observable getThatData = - /// memory.switchIfEmpty(disk).switchIfEmpty(network); - Observable switchIfEmpty(Stream fallbackStream) => - transform(SwitchIfEmptyStreamTransformer(fallbackStream)); - - /// Converts each emitted item into a new Stream using the given mapper - /// function. The newly created Stream will be be listened to and begin - /// emitting items, and any previously created Stream will stop emitting. - /// - /// The switchMap operator is similar to the flatMap and concatMap methods, - /// but it only emits items from the most recently created Stream. - /// - /// This can be useful when you only want the very latest state from - /// asynchronous APIs, for example. - /// - /// ### Example - /// - /// Observable.range(4, 1) - /// .switchMap((i) => - /// new Observable.timer(i, new Duration(minutes: i)) - /// .listen(print); // prints 1 - Observable switchMap(Stream mapper(T value)) => - transform(SwitchMapStreamTransformer(mapper)); - - /// Provides at most the first `n` values of this stream. - /// Forwards the first n data events of this stream, and all error events, to - /// the returned stream, and ends with a done event. - /// - /// If this stream produces fewer than count values before it's done, so will - /// the returned stream. - /// - /// Stops listening to the stream after the first n elements have been - /// received. - /// - /// Internally the method cancels its subscription after these elements. This - /// means that single-subscription (non-broadcast) streams are closed and - /// cannot be reused after a call to this method. - /// - /// The returned stream is a broadcast stream if this stream is. For a - /// broadcast stream, the events are only counted from the time the returned - /// stream is listened to - @override - Observable take(int count) => Observable(_stream.take(count)); - - /// Returns the values from the source observable sequence until the other - /// observable sequence produces a value. - /// - /// ### Example - /// - /// new Observable.merge([ - /// new Observable.just(1), - /// new Observable.timer(2, new Duration(minutes: 1)) - /// ]) - /// .takeUntil(new Observable.timer(3, new Duration(seconds: 10))) - /// .listen(print); // prints 1 - Observable takeUntil(Stream otherStream) => - transform(TakeUntilStreamTransformer(otherStream)); - - /// Forwards data events while test is successful. - /// - /// The returned stream provides the same events as this stream as long as - /// test returns true for the event data. The stream is done when either this - /// stream is done, or when this stream first provides a value that test - /// doesn't accept. - /// - /// Stops listening to the stream after the accepted elements. - /// - /// Internally the method cancels its subscription after these elements. This - /// means that single-subscription (non-broadcast) streams are closed and - /// cannot be reused after a call to this method. - /// - /// The returned stream is a broadcast stream if this stream is. For a - /// broadcast stream, the events are only tested from the time the returned - /// stream is listened to. - @override - Observable takeWhile(bool test(T element)) => - Observable(_stream.takeWhile(test)); - - /// Emits only the first item emitted by the source [Stream] - /// while [window] is open. - /// - /// if [trailing] is true, then the last item is emitted instead - /// - /// You can use the value of the last throttled event to determine - /// the length of the next [window]. - /// - /// ### Example - /// - /// new Stream.fromIterable([1, 2, 3]) - /// .throttle((_) => TimerStream(true, const Duration(seconds: 1))) - Observable throttle(Stream window(T event), {bool trailing = false}) => - transform(ThrottleStreamTransformer(window, trailing: trailing)); - - /// Emits only the first item emitted by the source [Stream] - /// within a time span of [duration]. - /// - /// if [trailing] is true, then the last item is emitted instead - /// - /// ### Example - /// - /// new Stream.fromIterable([1, 2, 3]) - /// .throttleTime(const Duration(seconds: 1)) - Observable throttleTime(Duration duration, {bool trailing = false}) => - transform(ThrottleStreamTransformer( - (_) => TimerStream(true, duration), - trailing: trailing)); - - /// Records the time interval between consecutive values in an observable - /// sequence. - /// - /// ### Example - /// - /// new Observable.just(1) - /// .interval(new Duration(seconds: 1)) - /// .timeInterval() - /// .listen(print); // prints TimeInterval{interval: 0:00:01, value: 1} - Observable> timeInterval() => - transform(TimeIntervalStreamTransformer()); - - /// The Timeout operator allows you to abort an Observable with an onError - /// termination if that Observable fails to emit any items during a specified - /// duration. You may optionally provide a callback function to execute on - /// timeout. - @override - Observable timeout(Duration timeLimit, - {void onTimeout(EventSink sink)}) => - Observable(_stream.timeout(timeLimit, onTimeout: onTimeout)); - - /// Wraps each item emitted by the source Observable in a [Timestamped] object - /// that includes the emitted item and the time when the item was emitted. - /// - /// Example - /// - /// new Observable.just(1) - /// .timestamp() - /// .listen((i) => print(i)); // prints 'TimeStamp{timestamp: XXX, value: 1}'; - Observable> timestamp() { - return transform(TimestampStreamTransformer()); - } - - @override - Observable transform(StreamTransformer streamTransformer) => - Observable(super.transform(streamTransformer)); - - @override - AsObservableFuture> toList() => - AsObservableFuture>(_stream.toList()); - - @override - AsObservableFuture> toSet() => - AsObservableFuture>(_stream.toSet()); - - /// Filters the elements of an observable sequence based on the test. - @override - Observable where(bool test(T event)) => Observable(_stream.where(test)); - - /// This transformer is a shorthand for [Stream.where] followed by [Stream.cast]. - /// - /// Events that do not match [T] are filtered out, the resulting - /// [Observable] will be of Type [T]. - /// - /// ### Example - /// - /// Observable.fromIterable([1, 'two', 3, 'four']) - /// .whereType() - /// .listen(print); // prints 1, 3 - /// - /// #### as opposed to: - /// - /// Observable.fromIterable([1, 'two', 3, 'four']) - /// .where((event) => event is int) - /// .cast() - /// .listen(print); // prints 1, 3 - Observable whereType() => transform(WhereTypeStreamTransformer()); - - /// Creates an Observable where each item is a [Stream] containing the items - /// from the source sequence. - /// - /// This [List] is emitted every time [window] emits an event. - /// - /// ### Example - /// - /// new Observable.periodic(const Duration(milliseconds: 100), (i) => i) - /// .window(new Stream.periodic(const Duration(milliseconds: 160), (i) => i)) - /// .asyncMap((stream) => stream.toList()) - /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... - Observable> window(Stream window) => - transform(WindowStreamTransformer((_) => window)); - - /// Buffers a number of values from the source Observable by [count] then - /// emits the buffer as a [Stream] and clears it, and starts a new buffer each - /// [startBufferEvery] values. If [startBufferEvery] is not provided, - /// then new buffers are started immediately at the start of the source - /// and when each buffer closes and is emitted. - /// - /// ### Example - /// [count] is the maximum size of the buffer emitted - /// - /// Observable.range(1, 4) - /// .windowCount(2) - /// .asyncMap((stream) => stream.toList()) - /// .listen(print); // prints [1, 2], [3, 4] done! - /// - /// ### Example - /// if [startBufferEvery] is 2, then a new buffer will be started - /// on every other value from the source. A new buffer is started at the - /// beginning of the source by default. - /// - /// Observable.range(1, 5) - /// .bufferCount(3, 2) - /// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! - Observable> windowCount(int count, [int startBufferEvery = 0]) => - transform(WindowCountStreamTransformer(count, startBufferEvery)); - - /// Creates an Observable where each item is a [Stream] containing the items - /// from the source sequence, batched whenever test passes. - /// - /// ### Example - /// - /// new Observable.periodic(const Duration(milliseconds: 100), (int i) => i) - /// .windowTest((i) => i % 2 == 0) - /// .asyncMap((stream) => stream.toList()) - /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... - Observable> windowTest(bool onTestHandler(T event)) => - transform(WindowTestStreamTransformer(onTestHandler)); - - /// Creates an Observable where each item is a [Stream] containing the items - /// from the source sequence, sampled on a time frame with [duration]. - /// - /// ### Example - /// - //// new Observable.periodic(const Duration(milliseconds: 100), (int i) => i) - /// .windowTime(const Duration(milliseconds: 220)) - /// .doOnData((_) => print('next window')) - /// .flatMap((s) => s) - /// .listen(print); // prints next window 0, 1, next window 2, 3, ... - Observable> windowTime(Duration duration) { - if (duration == null) throw ArgumentError.notNull('duration'); - - return window(Stream.periodic(duration)); - } - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the two streams using the provided function. - /// - /// If the latestFromStream has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// new Observable.fromIterable([1, 2]).withLatestFrom( - /// new Observable.fromIterable([2, 3]), (a, b) => a + b) - /// .listen(print); // prints 4 (due to the async nature of streams) - Observable withLatestFrom( - Stream latestFromStream, R fn(T t, S s)) => - transform(WithLatestFromStreamTransformer.with1(latestFromStream, fn)); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the streams into a list. This is helpful when you need to - /// combine a dynamic number of Streams. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// Observable.fromIterable([1, 2]).withLatestFromList( - /// [ - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// Observable.fromIterable([6, 7]), - /// ], - /// ).listen(print); // print [2, 2, 3, 4, 5, 6] (due to the async nature of streams) - /// - Observable> withLatestFromList( - Iterable> latestFromStreams) => - transform(WithLatestFromStreamTransformer.withList(latestFromStreams)); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the three streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom2( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// (int a, int b, int c) => a + b + c, - /// ) - /// .listen(print); // prints 7 (due to the async nature of streams) - Observable withLatestFrom2( - Stream latestFromStream1, - Stream latestFromStream2, - R fn(T t, A a, B b), - ) => - transform(WithLatestFromStreamTransformer.with2( - latestFromStream1, latestFromStream2, fn)); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the four streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom3( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// (int a, int b, int c, int d) => a + b + c + d, - /// ) - /// .listen(print); // prints 11 (due to the async nature of streams) - Observable withLatestFrom3( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - R fn(T t, A a, B b, C c), - ) => - transform(WithLatestFromStreamTransformer.with3( - latestFromStream1, - latestFromStream2, - latestFromStream3, - fn, - )); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the five streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom4( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// (int a, int b, int c, int d, int e) => a + b + c + d + e, - /// ) - /// .listen(print); // prints 16 (due to the async nature of streams) - Observable withLatestFrom4( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - R fn(T t, A a, B b, C c, D d), - ) => - transform(WithLatestFromStreamTransformer.with4( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - fn, - )); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the six streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom5( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// Observable.fromIterable([6, 7]), - /// (int a, int b, int c, int d, int e, int f) => a + b + c + d + e + f, - /// ) - /// .listen(print); // prints 22 (due to the async nature of streams) - Observable withLatestFrom5( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - R fn(T t, A a, B b, C c, D d, E e), - ) => - transform(WithLatestFromStreamTransformer.with5( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - fn, - )); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the seven streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom6( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// Observable.fromIterable([6, 7]), - /// Observable.fromIterable([7, 8]), - /// (int a, int b, int c, int d, int e, int f, int g) => - /// a + b + c + d + e + f + g, - /// ) - /// .listen(print); // prints 29 (due to the async nature of streams) - Observable withLatestFrom6( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - R fn(T t, A a, B b, C c, D d, E e, F f), - ) => - transform(WithLatestFromStreamTransformer.with6( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - fn, - )); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the eight streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom7( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// Observable.fromIterable([6, 7]), - /// Observable.fromIterable([7, 8]), - /// Observable.fromIterable([8, 9]), - /// (int a, int b, int c, int d, int e, int f, int g, int h) => - /// a + b + c + d + e + f + g + h, - /// ) - /// .listen(print); // prints 37 (due to the async nature of streams) - Observable withLatestFrom7( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - R fn(T t, A a, B b, C c, D d, E e, F f, G g), - ) => - transform(WithLatestFromStreamTransformer.with7( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - fn, - )); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the nine streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom8( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// Observable.fromIterable([6, 7]), - /// Observable.fromIterable([7, 8]), - /// Observable.fromIterable([8, 9]), - /// Observable.fromIterable([9, 10]), - /// (int a, int b, int c, int d, int e, int f, int g, int h, int i) => - /// a + b + c + d + e + f + g + h + i, - /// ) - /// .listen(print); // prints 46 (due to the async nature of streams) - Observable withLatestFrom8( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - Stream latestFromStream8, - R fn(T t, A a, B b, C c, D d, E e, F f, G g, H h), - ) => - transform(WithLatestFromStreamTransformer.with8( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - latestFromStream8, - fn, - )); - - /// Creates an Observable that emits when the source stream emits, combining - /// the latest values from the ten streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Observable.fromIterable([1, 2]) - /// .withLatestFrom9( - /// Observable.fromIterable([2, 3]), - /// Observable.fromIterable([3, 4]), - /// Observable.fromIterable([4, 5]), - /// Observable.fromIterable([5, 6]), - /// Observable.fromIterable([6, 7]), - /// Observable.fromIterable([7, 8]), - /// Observable.fromIterable([8, 9]), - /// Observable.fromIterable([9, 10]), - /// Observable.fromIterable([10, 11]), - /// (int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) => - /// a + b + c + d + e + f + g + h + i + j, - /// ) - /// .listen(print); // prints 46 (due to the async nature of streams) - Observable withLatestFrom9( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - Stream latestFromStream8, - Stream latestFromStream9, - R fn(T t, A a, B b, C c, D d, E e, F f, G g, H h, I i), - ) => - transform(WithLatestFromStreamTransformer.with9( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - latestFromStream8, - latestFromStream9, - fn, - )); - - /// Returns an Observable that combines the current stream together with - /// another stream using a given zipper function. - /// - /// ### Example - /// - /// new Observable.just(1) - /// .zipWith(new Observable.just(2), (one, two) => one + two) - /// .listen(print); // prints 3 - Observable zipWith(Stream other, R zipper(T t, S s)) => - Observable(ZipStream.zip2(_stream, other, zipper)); - - /// Convert the current Observable into a [ConnectableObservable] - /// that can be listened to multiple times. It will not begin emitting items - /// from the original Observable until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. - /// - /// ### Example - /// - /// ``` - /// final source = Observable.fromIterable([1, 2, 3]); - /// final connectable = source.publish(); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Observable. Will cause the previous - /// // line to start printing 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // Subject - /// subscription.cancel(); - /// ``` - ConnectableObservable publish() => PublishConnectableObservable(this); - - /// Convert the current Observable into a [ValueConnectableObservable] - /// that can be listened to multiple times. It will not begin emitting items - /// from the original Observable until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream that replays the latest emitted value to any new - /// listener. It also provides access to the latest value synchronously. - /// - /// ### Example - /// - /// ``` - /// final source = Observable.fromIterable([1, 2, 3]); - /// final connectable = source.publishValue(); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Observable. Will cause the previous - /// // line to start printing 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Late subscribers will receive the last emitted value - /// connectable.listen(print); // Prints 3 - /// - /// // Can access the latest emitted value synchronously. Prints 3 - /// print(connectable.value); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject - /// subscription.cancel(); - /// ``` - ValueConnectableObservable publishValue() => - ValueConnectableObservable(this); - - /// Convert the current Observable into a [ValueConnectableObservable] - /// that can be listened to multiple times, providing an initial seeded value. - /// It will not begin emitting items from the original Observable - /// until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream that replays the latest emitted value to any new - /// listener. It also provides access to the latest value synchronously. - /// - /// ### Example - /// - /// ``` - /// final source = Observable.fromIterable([1, 2, 3]); - /// final connectable = source.publishValueSeeded(0); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Observable. Will cause the previous - /// // line to start printing 0, 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Late subscribers will receive the last emitted value - /// connectable.listen(print); // Prints 3 - /// - /// // Can access the latest emitted value synchronously. Prints 3 - /// print(connectable.value); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject - /// subscription.cancel(); - /// ``` - ValueConnectableObservable publishValueSeeded(T seedValue) => - ValueConnectableObservable.seeded(this, seedValue); - - /// Convert the current Observable into a [ReplayConnectableObservable] - /// that can be listened to multiple times. It will not begin emitting items - /// from the original Observable until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream that replays a given number of items to any new - /// listener. It also provides access to the emitted values synchronously. - /// - /// ### Example - /// - /// ``` - /// final source = Observable.fromIterable([1, 2, 3]); - /// final connectable = source.publishReplay(); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Observable. Will cause the previous - /// // line to start printing 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Late subscribers will receive the emitted value, up to a specified - /// // maxSize - /// connectable.listen(print); // Prints 1, 2, 3 - /// - /// // Can access a list of the emitted values synchronously. Prints [1, 2, 3] - /// print(connectable.values); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // ReplaySubject - /// subscription.cancel(); - /// ``` - ReplayConnectableObservable publishReplay({int maxSize}) => - ReplayConnectableObservable(this, maxSize: maxSize); - - /// Convert the current Observable into a new Observable that can be listened - /// to multiple times. It will automatically begin emitting items when first - /// listened to, and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream - /// final observable = Observable.fromIterable([1, 2, 3]).share(); - /// - /// // Start listening to the source Observable. Will start printing 1, 2, 3 - /// final subscription = observable.listen(print); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // PublishSubject - /// subscription.cancel(); - /// ``` - Observable share() => publish().refCount(); - - /// Convert the current Observable into a new [ValueObservable] that can - /// be listened to multiple times. It will automatically begin emitting items - /// when first listened to, and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. It's also useful for providing sync access to the latest - /// emitted value. - /// - /// It will replay the latest emitted value to any new listener. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream that will emit the latest value to any new listeners - /// final observable = Observable.fromIterable([1, 2, 3]).shareValue(); - /// - /// // Start listening to the source Observable. Will start printing 1, 2, 3 - /// final subscription = observable.listen(print); - /// - /// // Synchronously print the latest value - /// print(observable.value); - /// - /// // Subscribe again later. This will print 3 because it receives the last - /// // emitted value. - /// final subscription2 = observable.listen(print); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject by cancelling all subscriptions. - /// subscription.cancel(); - /// subscription2.cancel(); - /// ``` - ValueObservable shareValue() => publishValue().refCount(); - - /// Convert the current Observable into a new [ValueObservable] that can - /// be listened to multiple times, providing an initial value. - /// It will automatically begin emitting items when first listened to, - /// and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. It's also useful for providing sync access to the latest - /// emitted value. - /// - /// It will replay the latest emitted value to any new listener. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream that will emit the latest value to any new listeners - /// final observable = Observable.fromIterable([1, 2, 3]).shareValueSeeded(0); - /// - /// // Start listening to the source Observable. Will start printing 0, 1, 2, 3 - /// final subscription = observable.listen(print); - /// - /// // Synchronously print the latest value - /// print(observable.value); - /// - /// // Subscribe again later. This will print 3 because it receives the last - /// // emitted value. - /// final subscription2 = observable.listen(print); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject by cancelling all subscriptions. - /// subscription.cancel(); - /// subscription2.cancel(); - /// ``` - ValueObservable shareValueSeeded(T seedValue) => - publishValueSeeded(seedValue).refCount(); - - /// Convert the current Observable into a new [ReplayObservable] that can - /// be listened to multiple times. It will automatically begin emitting items - /// when first listened to, and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. It's also useful for gaining access to the l - /// - /// It will replay the emitted values to any new listener, up to a given - /// [maxSize]. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream that will emit the latest value to any new listeners - /// final observable = Observable.fromIterable([1, 2, 3]).shareReplay(); - /// - /// // Start listening to the source Observable. Will start printing 1, 2, 3 - /// final subscription = observable.listen(print); - /// - /// // Synchronously print the emitted values up to a given maxSize - /// // Prints [1, 2, 3] - /// print(observable.values); - /// - /// // Subscribe again later. This will print 1, 2, 3 because it receives the - /// // last emitted value. - /// final subscription2 = observable.listen(print); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // ReplaySubject by cancelling all subscriptions. - /// subscription.cancel(); - /// subscription2.cancel(); - /// ``` - ReplayObservable shareReplay({int maxSize}) => - publishReplay(maxSize: maxSize).refCount(); } diff --git a/lib/src/streams/concat.dart b/lib/src/streams/concat.dart index 9a23b8c97..f17dbebdb 100644 --- a/lib/src/streams/concat.dart +++ b/lib/src/streams/concat.dart @@ -10,10 +10,10 @@ import 'dart:async'; /// /// ### Example /// -/// new ConcatStream([ -/// new Stream.fromIterable([1]), -/// new TimerStream(2, new Duration(days: 1)), -/// new Stream.fromIterable([3]) +/// ConcatStream([ +/// Stream.fromIterable([1]), +/// TimerStream(2, Duration(days: 1)), +/// Stream.fromIterable([3]) /// ]) /// .listen(print); // prints 1, 2, 3 class ConcatStream extends Stream { @@ -76,3 +76,19 @@ class ConcatStream extends Stream { return controller; } } + +/// Extends the Stream class with the ability to concatenate one stream with +/// another. +extension ConcatExtensions on Stream { + /// Returns a Stream that emits all items from the current Stream, + /// then emits all items from the given streams, one after the next. + /// + /// ### Example + /// + /// TimerStream(1, Duration(seconds: 10)) + /// .concatWith([Stream.fromIterable([2])]) + /// .listen(print); // prints 1, 2 + Stream concatWith(Iterable> other) => + transform(StreamTransformer.fromBind( + (stream) => ConcatStream([stream, ...other]))); +} diff --git a/lib/src/streams/concat_eager.dart b/lib/src/streams/concat_eager.dart index 10f5a056d..fd98a6cf9 100644 --- a/lib/src/streams/concat_eager.dart +++ b/lib/src/streams/concat_eager.dart @@ -14,10 +14,10 @@ import 'package:rxdart/src/streams/concat.dart'; /// /// ### Example /// -/// new ConcatEagerStream([ -/// new Stream.fromIterable([1]), -/// new TimerStream(2, new Duration(days: 1)), -/// new Stream.fromIterable([3]) +/// ConcatEagerStream([ +/// Stream.fromIterable([1]), +/// TimerStream(2, Duration(days: 1)), +/// Stream.fromIterable([3]) /// ]) /// .listen(print); // prints 1, 2, 3 class ConcatEagerStream extends Stream { diff --git a/lib/src/streams/defer.dart b/lib/src/streams/defer.dart index 546262806..e7997d248 100644 --- a/lib/src/streams/defer.dart +++ b/lib/src/streams/defer.dart @@ -12,7 +12,7 @@ import 'dart:async'; /// /// ### Example /// -/// new DeferStream(() => new Observable.just(1)).listen(print); //prints 1 +/// DeferStream(() => Observable.just(1)).listen(print); //prints 1 class DeferStream extends Stream { final Stream Function() _factory; final bool _isReusable; diff --git a/lib/src/streams/error.dart b/lib/src/streams/error.dart index e76fe418e..96d793dc3 100644 --- a/lib/src/streams/error.dart +++ b/lib/src/streams/error.dart @@ -8,7 +8,7 @@ import 'dart:async'; /// /// ### Example /// -/// new ErrorStream(new ArgumentError()); +/// ErrorStream(ArgumentError()); class ErrorStream extends Stream { /// Reference to the wrapped error final Object error; diff --git a/lib/src/streams/merge.dart b/lib/src/streams/merge.dart index e71f27f3e..c415829f2 100644 --- a/lib/src/streams/merge.dart +++ b/lib/src/streams/merge.dart @@ -7,9 +7,9 @@ import 'dart:async'; /// /// ### Example /// -/// new MergeStream([ -/// new TimerStream(1, new Duration(days: 10)), -/// new Stream.fromIterable([2]) +/// MergeStream([ +/// TimerStream(1, Duration(days: 10)), +/// Stream.fromIterable([2]) /// ]) /// .listen(print); // prints 2, 1 class MergeStream extends Stream { @@ -68,3 +68,19 @@ class MergeStream extends Stream { return controller; } } + +/// Extends the Stream class with the ability to merge one stream with another. +extension MergeExtension on Stream { + /// Combines the items emitted by multiple streams into a single stream of + /// items. The items are emitted in the order they are emitted by their + /// sources. + /// + /// ### Example + /// + /// TimerStream(1, Duration(seconds: 10)) + /// .mergeWith([Stream.fromIterable([2])]) + /// .listen(print); // prints 2, 1 + Stream mergeWith(Iterable> streams) => + transform(StreamTransformer.fromBind( + (stream) => MergeStream([stream, ...streams]))); +} diff --git a/lib/src/streams/never.dart b/lib/src/streams/never.dart index 49b60e3ba..6028e8f92 100644 --- a/lib/src/streams/never.dart +++ b/lib/src/streams/never.dart @@ -10,7 +10,7 @@ import 'dart:async'; /// /// ### Example /// -/// new NeverStream().listen(print); // Neither prints nor terminates +/// NeverStream().listen(print); // Neither prints nor terminates class NeverStream extends Stream { // ignore: close_sinks StreamController _controller = StreamController(); diff --git a/lib/src/streams/race.dart b/lib/src/streams/race.dart index c14b5e27e..f51593769 100644 --- a/lib/src/streams/race.dart +++ b/lib/src/streams/race.dart @@ -7,10 +7,10 @@ import 'dart:async'; /// /// ### Example /// -/// new RaceStream([ -/// new TimerStream(1, new Duration(days: 1)), -/// new TimerStream(2, new Duration(days: 2)), -/// new TimerStream(3, new Duration(seconds: 3)) +/// RaceStream([ +/// TimerStream(1, Duration(days: 1)), +/// TimerStream(2, Duration(days: 2)), +/// TimerStream(3, Duration(seconds: 3)) /// ]).listen(print); // prints 3 class RaceStream extends Stream { final StreamController _controller; diff --git a/lib/src/streams/range.dart b/lib/src/streams/range.dart index 31d210ec7..1010e4b6a 100644 --- a/lib/src/streams/range.dart +++ b/lib/src/streams/range.dart @@ -5,9 +5,9 @@ import 'dart:async'; /// /// ### Examples /// -/// new RangeStream(1, 3).listen((i) => print(i)); // Prints 1, 2, 3 +/// RangeStream(1, 3).listen((i) => print(i)); // Prints 1, 2, 3 /// -/// new RangeStream(3, 1).listen((i) => print(i)); // Prints 3, 2, 1 +/// RangeStream(3, 1).listen((i) => print(i)); // Prints 3, 2, 1 class RangeStream extends Stream { final Stream _stream; diff --git a/lib/src/streams/repeat.dart b/lib/src/streams/repeat.dart index 33431e182..2de412f91 100644 --- a/lib/src/streams/repeat.dart +++ b/lib/src/streams/repeat.dart @@ -8,7 +8,7 @@ import 'dart:async'; /// /// ### Example /// -/// new RepeatStream((int repeatCount) => +/// RepeatStream((int repeatCount) => /// Observable.just('repeat index: $repeatCount'), 3) /// .listen((i) => print(i)); // Prints 'repeat index: 0, repeat index: 1, repeat index: 2' class RepeatStream extends Stream { diff --git a/lib/src/streams/retry.dart b/lib/src/streams/retry.dart index c9c06d4af..efc8e5354 100644 --- a/lib/src/streams/retry.dart +++ b/lib/src/streams/retry.dart @@ -13,12 +13,12 @@ import 'package:rxdart/src/streams/utils.dart'; /// /// ### Example /// -/// new RetryStream(() { new Stream.fromIterable([1]); }) +/// RetryStream(() { Stream.fromIterable([1]); }) /// .listen((i) => print(i)); // Prints 1 /// -/// new RetryStream(() { -/// new Stream.fromIterable([1]) -/// .concatWith([new ErrorStream(new Error())]); +/// RetryStream(() { +/// Stream.fromIterable([1]) +/// .concatWith([ErrorStream(Error())]); /// }, 1) /// .listen(print, onError: (e, s) => print(e)); // Prints 1, 1, RetryError class RetryStream extends Stream { diff --git a/lib/src/streams/retry_when.dart b/lib/src/streams/retry_when.dart index 646926618..e6b882db3 100644 --- a/lib/src/streams/retry_when.dart +++ b/lib/src/streams/retry_when.dart @@ -12,20 +12,20 @@ import 'package:rxdart/src/streams/utils.dart'; /// /// ### Basic Example /// ```dart -/// new RetryWhenStream( -/// () => new Stream.fromIterable([1]), +/// RetryWhenStream( +/// () => Stream.fromIterable([1]), /// (dynamic error, StackTrace s) => throw error, /// ).listen(print); // Prints 1 /// ``` /// /// ### Periodic Example /// ```dart -/// new RetryWhenStream( -/// () => new Observable +/// RetryWhenStream( +/// () => Observable /// .periodic(const Duration(seconds: 1), (int i) => i) /// .map((int i) => i == 2 ? throw 'exception' : i), /// (dynamic e, StackTrace s) { -/// return new Observable +/// return Observable /// .timer('random value', const Duration(milliseconds: 200)); /// }, /// ).take(4).listen(print); // Prints 0, 1, 0, 1 @@ -34,8 +34,8 @@ import 'package:rxdart/src/streams/utils.dart'; /// ### Complex Example /// ```dart /// bool errorHappened = false; -/// new RetryWhenStream( -/// () => new Observable +/// RetryWhenStream( +/// () => Observable /// .periodic(const Duration(seconds: 1), (i) => i) /// .map((i) { /// if (i == 3 && !errorHappened) { @@ -49,9 +49,9 @@ import 'package:rxdart/src/streams/utils.dart'; /// (e, s) { /// errorHappened = true; /// if (e == 'We can take this. Please restart.') { -/// return new Observable.just('Ok. Here you go!'); +/// return Observable.just('Ok. Here you go!'); /// } else { -/// return new Observable.error(e); +/// return Observable.error(e); /// } /// }, /// ).listen( diff --git a/lib/src/streams/sequence_equal.dart b/lib/src/streams/sequence_equal.dart index 09ca44b91..50baeefe2 100644 --- a/lib/src/streams/sequence_equal.dart +++ b/lib/src/streams/sequence_equal.dart @@ -13,7 +13,7 @@ import 'package:rxdart/src/utils/notification.dart'; /// /// ### Example /// -/// new SequenceEqualsStream([ +/// SequenceEqualsStream([ /// Stream.fromIterable([1, 2, 3, 4, 5]), /// Stream.fromIterable([1, 2, 3, 4, 5]) /// ]) diff --git a/lib/src/streams/switch_latest.dart b/lib/src/streams/switch_latest.dart index 5b83270e0..f51a4ef90 100644 --- a/lib/src/streams/switch_latest.dart +++ b/lib/src/streams/switch_latest.dart @@ -10,11 +10,11 @@ import 'dart:async'; /// ### Example /// /// ```dart -/// final switchLatestStream = new SwitchLatestStream( -/// new Stream.fromIterable(>[ -/// new Observable.timer('A', new Duration(seconds: 2)), -/// new Observable.timer('B', new Duration(seconds: 1)), -/// new Observable.just('C'), +/// final switchLatestStream = SwitchLatestStream( +/// Stream.fromIterable(>[ +/// Observable.timer('A', Duration(seconds: 2)), +/// Observable.timer('B', Duration(seconds: 1)), +/// Observable.just('C'), /// ]), /// ); /// diff --git a/lib/src/streams/timer.dart b/lib/src/streams/timer.dart index 64bea0c8b..ccf6e66e3 100644 --- a/lib/src/streams/timer.dart +++ b/lib/src/streams/timer.dart @@ -4,7 +4,7 @@ import 'dart:async'; /// /// ### Example /// -/// new TimerStream("hi", new Duration(minutes: 1)) +/// TimerStream("hi", Duration(minutes: 1)) /// .listen((i) => print(i)); // print "hi" after 1 minute class TimerStream extends Stream { final StreamController _controller; diff --git a/lib/src/streams/zip.dart b/lib/src/streams/zip.dart index b4ea99057..523827e57 100644 --- a/lib/src/streams/zip.dart +++ b/lib/src/streams/zip.dart @@ -369,3 +369,18 @@ class _Window { return List.unmodifiable(_values); } } + +/// Extends the Stream class with the ability to zip one Stream with another. +extension ZipWithExtension on Stream { + /// Returns a Stream that combines the current stream together with another + /// stream using a given zipper function. + /// + /// ### Example + /// + /// Stream.fromIterable([1]) + /// .zipWith(Stream.fromIterable([2]), (one, two) => one + two) + /// .listen(print); // prints 3 + Stream zipWith(Stream other, R zipper(T t, S s)) => + transform(StreamTransformer.fromBind( + (stream) => ZipStream.zip2(stream, other, zipper))); +} diff --git a/lib/src/subjects/behavior_subject.dart b/lib/src/subjects/behavior_subject.dart index eccee8cc2..7fc717969 100644 --- a/lib/src/subjects/behavior_subject.dart +++ b/lib/src/subjects/behavior_subject.dart @@ -22,7 +22,7 @@ import 'package:rxdart/src/transformers/start_with_error.dart'; /// /// ### Example /// -/// final subject = new BehaviorSubject(); +/// final subject = BehaviorSubject(); /// /// subject.add(1); /// subject.add(2); @@ -34,7 +34,7 @@ import 'package:rxdart/src/transformers/start_with_error.dart'; /// /// ### Example with seed value /// -/// final subject = new BehaviorSubject.seeded(1); +/// final subject = BehaviorSubject.seeded(1); /// /// subject.stream.listen(print); // prints 1 /// subject.stream.listen(print); // prints 1 diff --git a/lib/src/subjects/publish_subject.dart b/lib/src/subjects/publish_subject.dart index facc9f62e..2c2b5b3a8 100644 --- a/lib/src/subjects/publish_subject.dart +++ b/lib/src/subjects/publish_subject.dart @@ -14,7 +14,7 @@ import 'package:rxdart/src/subjects/subject.dart'; /// /// ### Example /// -/// final subject = new PublishSubject(); +/// final subject = PublishSubject(); /// /// // observer1 will receive all data and done events /// subject.stream.listen(observer1); diff --git a/lib/src/subjects/replay_subject.dart b/lib/src/subjects/replay_subject.dart index 53d0a3215..295b42c90 100644 --- a/lib/src/subjects/replay_subject.dart +++ b/lib/src/subjects/replay_subject.dart @@ -4,6 +4,7 @@ import 'dart:collection'; import 'package:rxdart/src/observables/observable.dart'; import 'package:rxdart/src/observables/replay_observable.dart'; import 'package:rxdart/src/subjects/subject.dart'; +import 'package:rxdart/src/transformers/start_with_many.dart'; /// A special StreamController that captures all of the items that have been /// added to the controller, and emits those as the first items to any new @@ -22,7 +23,7 @@ import 'package:rxdart/src/subjects/subject.dart'; /// /// ### Example /// -/// final subject = new ReplaySubject(); +/// final subject = ReplaySubject(); /// /// subject.add(1); /// subject.add(2); @@ -34,7 +35,7 @@ import 'package:rxdart/src/subjects/subject.dart'; /// /// ### Example with maxSize /// -/// final subject = new ReplaySubject(maxSize: 2); +/// final subject = ReplaySubject(maxSize: 2); /// /// subject.add(1); /// subject.add(2); diff --git a/lib/src/transformers/backpressure/buffer.dart b/lib/src/transformers/backpressure/buffer.dart index a369a9b96..f05526844 100644 --- a/lib/src/transformers/backpressure/buffer.dart +++ b/lib/src/transformers/backpressure/buffer.dart @@ -10,8 +10,8 @@ import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// /// ### Example /// -/// new Observable.periodic(const Duration(milliseconds: 100), (i) => i) -/// .buffer(new Stream.periodic(const Duration(milliseconds: 160), (i) => i)) +/// Observable.periodic(const Duration(milliseconds: 100), (i) => i) +/// .buffer(Stream.periodic(const Duration(milliseconds: 160), (i) => i)) /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... class BufferStreamTransformer extends BackpressureStreamTransformer> { @@ -79,7 +79,7 @@ class BufferCountStreamTransformer /// /// ### Example /// -/// new Observable.periodic(const Duration(milliseconds: 100), (int i) => i) +/// Observable.periodic(const Duration(milliseconds: 100), (int i) => i) /// .bufferTest((i) => i % 2 == 0) /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... class BufferTestStreamTransformer @@ -93,3 +93,69 @@ class BufferTestStreamTransformer if (test == null) throw ArgumentError.notNull('test'); } } + + +/// Extends the Stream class with the ability to buffer events in various ways +extension BufferExtensions on Stream { + /// Creates a Stream where each item is a [List] containing the items + /// from the source sequence. + /// + /// This [List] is emitted every time [window] emits an event. + /// + /// ### Example + /// + /// Stream.periodic(Duration(milliseconds: 100), (i) => i) + /// .buffer(Stream.periodic(Duration(milliseconds: 160), (i) => i)) + /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... + Stream> buffer(Stream window) => + transform(BufferStreamTransformer((_) => window)); + + /// Buffers a number of values from the source Stream by [count] then + /// emits the buffer and clears it, and starts a new buffer each + /// [startBufferEvery] values. If [startBufferEvery] is not provided, + /// then new buffers are started immediately at the start of the source + /// and when each buffer closes and is emitted. + /// + /// ### Example + /// [count] is the maximum size of the buffer emitted + /// + /// RangeStream(1, 4) + /// .bufferCount(2) + /// .listen(print); // prints [1, 2], [3, 4] done! + /// + /// ### Example + /// if [startBufferEvery] is 2, then a new buffer will be started + /// on every other value from the source. A new buffer is started at the + /// beginning of the source by default. + /// + /// RangeStream(1, 5) + /// .bufferCount(3, 2) + /// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! + Stream> bufferCount(int count, [int startBufferEvery = 0]) => + transform(BufferCountStreamTransformer(count, startBufferEvery)); + + /// Creates a Stream where each item is a [List] containing the items + /// from the source sequence, batched whenever test passes. + /// + /// ### Example + /// + /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) + /// .bufferTest((i) => i % 2 == 0) + /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... + Stream> bufferTest(bool onTestHandler(T event)) => + transform(BufferTestStreamTransformer(onTestHandler)); + + /// Creates a Stream where each item is a [List] containing the items + /// from the source sequence, sampled on a time frame with [duration]. + /// + /// ### Example + /// + /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) + /// .bufferTime(Duration(milliseconds: 220)) + /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... + Stream> bufferTime(Duration duration) { + if (duration == null) throw ArgumentError.notNull('duration'); + + return buffer(Stream.periodic(duration)); + } +} diff --git a/lib/src/transformers/backpressure/debounce.dart b/lib/src/transformers/backpressure/debounce.dart index 2522b9e9d..e279bcf0f 100644 --- a/lib/src/transformers/backpressure/debounce.dart +++ b/lib/src/transformers/backpressure/debounce.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:rxdart/src/streams/timer.dart'; import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// Transforms a [Stream] so that will only emit items from the source sequence @@ -20,8 +21,8 @@ import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// /// ### Example /// -/// new Observable.fromIterable([1, 2, 3, 4]) -/// .debounceTime(new Duration(seconds: 1)) +/// Observable.fromIterable([1, 2, 3, 4]) +/// .debounceTime(Duration(seconds: 1)) /// .listen(print); // prints 4 class DebounceStreamTransformer extends BackpressureStreamTransformer { /// Constructs a [StreamTransformer] which buffers events into a [List] and @@ -35,3 +36,48 @@ class DebounceStreamTransformer extends BackpressureStreamTransformer { assert(window != null, 'window stream factory cannot be null'); } } + +/// Extends the Stream class with the ability to debounce events in various ways +extension DebounceExtensions on Stream { + /// Transforms a [Stream] so that will only emit items from the source sequence + /// if a [window] has completed, without the source sequence emitting + /// another item. + /// + /// This [window] is created after the last debounced event was emitted. + /// You can use the value of the last debounced event to determine + /// the length of the next [window]. + /// + /// A [window] is open until the first [window] event emits. + /// + /// debounce filters out items emitted by the source [Stream] + /// that are rapidly followed by another emitted item. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#debounce) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3, 4]) + /// .debounce((_) => TimerStream(true, Duration(seconds: 1))) + /// .listen(print); // prints 4 + Stream debounce(Stream window(T event)) => + transform(DebounceStreamTransformer(window)); + + /// Transforms a [Stream] so that will only emit items from the source + /// sequence whenever the time span defined by [duration] passes, without the + /// source sequence emitting another item. + /// + /// This time span start after the last debounced event was emitted. + /// + /// debounceTime filters out items emitted by the source [Stream] that are + /// rapidly followed by another emitted item. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#debounce) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3, 4]) + /// .debounceTime(Duration(seconds: 1)) + /// .listen(print); // prints 4 + Stream debounceTime(Duration duration) => transform( + DebounceStreamTransformer((_) => TimerStream(true, duration))); +} diff --git a/lib/src/transformers/backpressure/pairwise.dart b/lib/src/transformers/backpressure/pairwise.dart index 5a1b007e5..c96efacf9 100644 --- a/lib/src/transformers/backpressure/pairwise.dart +++ b/lib/src/transformers/backpressure/pairwise.dart @@ -18,3 +18,16 @@ class PairwiseStreamTransformer closeWindowWhen: (Iterable queue) => queue.length == 2, dispatchOnClose: false); } + +/// Extends the Stream class with the ability to emit the nth and n-1th events +/// as a pair +extension PairwiseExtension on Stream { + /// Emits the n-th and n-1th events as a pair.. + /// + /// ### Example + /// + /// RangeStream(1, 4) + /// .pairwise() + /// .listen(print); // prints [1, 2], [2, 3], [3, 4] + Stream> pairwise() => transform(PairwiseStreamTransformer()); +} diff --git a/lib/src/transformers/backpressure/sample.dart b/lib/src/transformers/backpressure/sample.dart index 857f62054..6119e536d 100644 --- a/lib/src/transformers/backpressure/sample.dart +++ b/lib/src/transformers/backpressure/sample.dart @@ -9,8 +9,8 @@ import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// /// ### Example /// -/// new Stream.fromIterable([1, 2, 3]) -/// .transform(new SampleStreamTransformer(new TimerStream(1, const Duration(seconds: 1))) +/// Stream.fromIterable([1, 2, 3]) +/// .transform(SampleStreamTransformer(TimerStream(1, const Duration(seconds: 1))) /// .listen(print); // prints 3 class SampleStreamTransformer extends BackpressureStreamTransformer { /// Constructs a [StreamTransformer] that, when the specified [window] emits @@ -23,3 +23,30 @@ class SampleStreamTransformer extends BackpressureStreamTransformer { assert(window != null, 'window stream factory cannot be null'); } } + +/// Extends the Stream class with the ability to sample events from the Stream +extension SampleExtensions on Stream { + /// Emits the most recently emitted item (if any) + /// emitted by the source [Stream] since the previous emission from + /// the [sampleStream]. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .sample(TimerStream(1, Duration(seconds: 1))) + /// .listen(print); // prints 3 + Stream sample(Stream sampleStream) => + transform(SampleStreamTransformer((_) => sampleStream)); + + /// Emits the most recently emitted item (if any) emitted by the source + /// [Stream] since the previous emission within the recurring time span, + /// defined by [duration] + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .sampleTime(Duration(seconds: 1)) + /// .listen(print); // prints 3 + Stream sampleTime(Duration duration) => + sample(Stream.periodic(duration)); +} diff --git a/lib/src/transformers/backpressure/throttle.dart b/lib/src/transformers/backpressure/throttle.dart index ca3c6df20..00b2a80f3 100644 --- a/lib/src/transformers/backpressure/throttle.dart +++ b/lib/src/transformers/backpressure/throttle.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:rxdart/src/streams/timer.dart'; import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// A [StreamTransformer] that emits only the first item emitted by the source @@ -9,8 +10,8 @@ import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// /// ### Example /// -/// new Stream.fromIterable([1, 2, 3]) -/// .transform(new ThrottleStreamTransformer((_) => TimerStream(true, const Duration(seconds: 1)))) +/// Stream.fromIterable([1, 2, 3]) +/// .transform(ThrottleStreamTransformer((_) => TimerStream(true, const Duration(seconds: 1)))) /// .listen(print); // prints 1 class ThrottleStreamTransformer extends BackpressureStreamTransformer { /// A [StreamTransformer] that emits only the first item emitted by the source @@ -24,3 +25,35 @@ class ThrottleStreamTransformer extends BackpressureStreamTransformer { assert(window != null, 'window stream factory cannot be null'); } } + +/// Extends the Stream class with the ability to throttle events in various ways +extension ThrottleExtensions on Stream { + /// Emits only the first item emitted by the source [Stream] while [window] is + /// open. + /// + /// if [trailing] is true, then the last item is emitted instead + /// + /// You can use the value of the last throttled event to determine the length + /// of the next [window]. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .throttle((_) => TimerStream(true, Duration(seconds: 1))) + Stream throttle(Stream window(T event), {bool trailing = false}) => + transform(ThrottleStreamTransformer(window, trailing: trailing)); + + /// Emits only the first item emitted by the source [Stream] within a time + /// span of [duration]. + /// + /// if [trailing] is true, then the last item is emitted instead + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .throttleTime(Duration(seconds: 1)) + Stream throttleTime(Duration duration, {bool trailing = false}) => + transform(ThrottleStreamTransformer( + (_) => TimerStream(true, duration), + trailing: trailing)); +} diff --git a/lib/src/transformers/backpressure/window.dart b/lib/src/transformers/backpressure/window.dart index b366feb61..f214ae182 100644 --- a/lib/src/transformers/backpressure/window.dart +++ b/lib/src/transformers/backpressure/window.dart @@ -10,8 +10,8 @@ import 'package:rxdart/src/transformers/backpressure/backpressure.dart'; /// /// ### Example /// -/// new Observable.periodic(const Duration(milliseconds: 100), (i) => i) -/// .window(new Stream.periodic(const Duration(milliseconds: 160), (i) => i)) +/// Observable.periodic(const Duration(milliseconds: 100), (i) => i) +/// .window(Stream.periodic(const Duration(milliseconds: 160), (i) => i)) /// .asyncMap((stream) => stream.toList()) /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... class WindowStreamTransformer @@ -82,7 +82,7 @@ class WindowCountStreamTransformer /// /// ### Example /// -/// new Observable.periodic(const Duration(milliseconds: 100), (int i) => i) +/// Observable.periodic(const Duration(milliseconds: 100), (int i) => i) /// .windowTest((i) => i % 2 == 0) /// .asyncMap((stream) => stream.toList()) /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... @@ -97,3 +97,73 @@ class WindowTestStreamTransformer if (test == null) throw ArgumentError.notNull('test'); } } + +/// Extends the Stream class with the ability to window +extension WindowExtensions on Stream { + /// Creates a Stream where each item is a [Stream] containing the items from + /// the source sequence. + /// + /// This [List] is emitted every time [window] emits an event. + /// + /// ### Example + /// + /// Stream.periodic(Duration(milliseconds: 100), (i) => i) + /// .window(Stream.periodic(Duration(milliseconds: 160), (i) => i)) + /// .asyncMap((stream) => stream.toList()) + /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... + Stream> window(Stream window) => + transform(WindowStreamTransformer((_) => window)); + + /// Buffers a number of values from the source Stream by [count] then emits + /// the buffer as a [Stream] and clears it, and starts a new buffer each + /// [startBufferEvery] values. If [startBufferEvery] is not provided, then new + /// buffers are started immediately at the start of the source and when each + /// buffer closes and is emitted. + /// + /// ### Example + /// [count] is the maximum size of the buffer emitted + /// + /// RangeStream(1, 4) + /// .windowCount(2) + /// .asyncMap((stream) => stream.toList()) + /// .listen(print); // prints [1, 2], [3, 4] done! + /// + /// ### Example + /// if [startBufferEvery] is 2, then a new buffer will be started + /// on every other value from the source. A new buffer is started at the + /// beginning of the source by default. + /// + /// RangeStream(1, 5) + /// .bufferCount(3, 2) + /// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! + Stream> windowCount(int count, [int startBufferEvery = 0]) => + transform(WindowCountStreamTransformer(count, startBufferEvery)); + + /// Creates a Stream where each item is a [Stream] containing the items from + /// the source sequence, batched whenever test passes. + /// + /// ### Example + /// + /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) + /// .windowTest((i) => i % 2 == 0) + /// .asyncMap((stream) => stream.toList()) + /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... + Stream> windowTest(bool onTestHandler(T event)) => + transform(WindowTestStreamTransformer(onTestHandler)); + + /// Creates a Stream where each item is a [Stream] containing the items from + /// the source sequence, sampled on a time frame with [duration]. + /// + /// ### Example + /// + /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) + /// .windowTime(Duration(milliseconds: 220)) + /// .doOnData((_) => print('next window')) + /// .flatMap((s) => s) + /// .listen(print); // prints next window 0, 1, next window 2, 3, ... + Stream> windowTime(Duration duration) { + if (duration == null) throw ArgumentError.notNull('duration'); + + return window(Stream.periodic(duration)); + } +} diff --git a/lib/src/transformers/default_if_empty.dart b/lib/src/transformers/default_if_empty.dart index 27b82c160..401ed0fd0 100644 --- a/lib/src/transformers/default_if_empty.dart +++ b/lib/src/transformers/default_if_empty.dart @@ -5,8 +5,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.empty() -/// .transform(new DefaultIfEmptyStreamTransformer(10)) +/// Stream.empty() +/// .transform(DefaultIfEmptyStreamTransformer(10)) /// .listen(print); // prints 10 class DefaultIfEmptyStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -53,3 +53,15 @@ class DefaultIfEmptyStreamTransformer extends StreamTransformerBase { }); } } + +/// +extension DefaultIfEmptyExtension on Stream { + /// Emit items from the source Stream, or a single default item if the source + /// Stream emits nothing. + /// + /// ### Example + /// + /// Stream.empty().defaultIfEmpty(10).listen(print); // prints 10 + Stream defaultIfEmpty(T defaultValue) => + transform(DefaultIfEmptyStreamTransformer(defaultValue)); +} diff --git a/lib/src/transformers/delay.dart b/lib/src/transformers/delay.dart index 86c8160f6..8a79240e3 100644 --- a/lib/src/transformers/delay.dart +++ b/lib/src/transformers/delay.dart @@ -10,8 +10,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Observable.fromIterable([1, 2, 3, 4]) -/// .delay(new Duration(seconds: 1)) +/// Observable.fromIterable([1, 2, 3, 4]) +/// .delay(Duration(seconds: 1)) /// .listen(print); // [after one second delay] prints 1, 2, 3, 4 immediately class DelayStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -82,3 +82,21 @@ class DelayStreamTransformer extends StreamTransformerBase { } } } + +/// Extends the Stream class with the ability to delay events being emitted +extension DelayExtension on Stream { + /// The Delay operator modifies its source Stream by pausing for a particular + /// increment of time (that you specify) before emitting each of the source + /// Stream’s items. This has the effect of shifting the entire sequence of + /// items emitted by the Stream forward in time by that specified increment. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#delay) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3, 4]) + /// .delay(Duration(seconds: 1)) + /// .listen(print); // [after one second delay] prints 1, 2, 3, 4 immediately + Stream delay(Duration duration) => + transform(DelayStreamTransformer(duration)); +} diff --git a/lib/src/transformers/dematerialize.dart b/lib/src/transformers/dematerialize.dart index a68dee244..e17153568 100644 --- a/lib/src/transformers/dematerialize.dart +++ b/lib/src/transformers/dematerialize.dart @@ -11,15 +11,15 @@ import 'package:rxdart/src/utils/notification.dart'; /// /// ### Example /// -/// new Stream> -/// .fromIterable([new Notification.onData(1), new Notification.onDone()]) +/// Stream> +/// .fromIterable([Notification.onData(1), Notification.onDone()]) /// .transform(dematerializeTransformer()) /// .listen((i) => print(i)); // Prints 1 /// /// ### Error example /// -/// new Stream> -/// .fromIterable([new Notification.onError(new Exception(), null)]) +/// Stream> +/// .fromIterable([Notification.onError(Exception(), null)]) /// .transform(dematerializeTransformer()) /// .listen(null, onError: (e, s) { print(e) }); // Prints Exception class DematerializeStreamTransformer @@ -75,3 +75,31 @@ class DematerializeStreamTransformer }); } } + +/// Converts the onData, onDone, and onError [Notification]s from a +/// materialized stream into normal onData, onDone, and onError events. +extension DematerializeExtension on Stream> { + /// Converts the onData, onDone, and onError [Notification] objects from a + /// materialized stream into normal onData, onDone, and onError events. + /// + /// When a stream has been materialized, it emits onData, onDone, and onError + /// events as [Notification] objects. Dematerialize simply reverses this by + /// transforming [Notification] objects back to a normal stream of events. + /// + /// ### Example + /// + /// Stream> + /// .fromIterable([Notification.onData(1), Notification.onDone()]) + /// .dematerialize() + /// .listen((i) => print(i)); // Prints 1 + /// + /// ### Error example + /// + /// Stream> + /// .fromIterable([Notification.onError(Exception(), null)]) + /// .dematerialize() + /// .listen(null, onError: (e, s) { print(e) }); // Prints Exception + Stream dematerialize() { + return transform(DematerializeStreamTransformer()); + } +} diff --git a/lib/src/transformers/distinct_unique.dart b/lib/src/transformers/distinct_unique.dart index b4c670d9c..277c4865e 100644 --- a/lib/src/transformers/distinct_unique.dart +++ b/lib/src/transformers/distinct_unique.dart @@ -9,7 +9,7 @@ import 'dart:collection'; /// /// ### Example /// -/// new Stream.fromIterable([1, 2, 1, 2, 1, 2, 3, 2, 1]) +/// Stream.fromIterable([1, 2, 1, 2, 1, 2, 3, 2, 1]) /// .listen((event) => print(event)); /// /// will emit: @@ -68,3 +68,23 @@ class DistinctUniqueStreamTransformer extends StreamTransformerBase { }); } } +/// Extends the Stream class with the ability to skip items that have previously +/// been emitted. +extension DistinctUniqueExtension on Stream { + /// WARNING: More commonly known as distinct in other Rx implementations. + /// Creates a Stream where data events are skipped if they have already + /// been emitted before. + /// + /// Equality is determined by the provided equals and hashCode methods. + /// If these are omitted, the '==' operator and hashCode on the last provided + /// data element are used. + /// + /// The returned stream is a broadcast stream if this stream is. If a + /// broadcast stream is listened to more than once, each subscription will + /// individually perform the equals and hashCode tests. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#distinct) + Stream distinctUnique({bool equals(T e1, T e2), int hashCode(T e)}) => + transform(DistinctUniqueStreamTransformer( + equals: equals, hashCode: hashCode)); +} diff --git a/lib/src/transformers/do.dart b/lib/src/transformers/do.dart index 2df671e9f..822d90239 100644 --- a/lib/src/transformers/do.dart +++ b/lib/src/transformers/do.dart @@ -31,8 +31,8 @@ import 'package:rxdart/src/utils/notification.dart'; /// /// ### Example /// -/// new Stream.fromIterable([1]) -/// .transform(new DoStreamTransformer( +/// Stream.fromIterable([1]) +/// .transform(DoStreamTransformer( /// onData: print, /// onError: (e, s) => print("Oh no!"), /// onDone: () => print("Done"))) @@ -218,3 +218,105 @@ class DoStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to execute a callback function +/// at different points in the Stream's lifecycle. +extension DoExtensions on Stream { + /// Invokes the given callback function when the stream subscription is + /// cancelled. Often called doOnUnsubscribe or doOnDispose in other + /// implementations. + /// + /// ### Example + /// + /// final subscription = TimerStream(1, Duration(minutes: 1)) + /// .doOnCancel(() => print("hi")); + /// .listen(null); + /// + /// subscription.cancel(); // prints "hi" + Stream doOnCancel(void onCancel()) => + transform(DoStreamTransformer(onCancel: onCancel)); + + /// Invokes the given callback function when the stream emits an item. In + /// other implementations, this is called doOnNext. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .doOnData(print) + /// .listen(null); // prints 1, 2, 3 + Stream doOnData(void onData(T event)) => + transform(DoStreamTransformer(onData: onData)); + + /// Invokes the given callback function when the stream finishes emitting + /// items. In other implementations, this is called doOnComplete(d). + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .doOnDone(() => print("all set")) + /// .listen(null); // prints "all set" + Stream doOnDone(void onDone()) => + transform(DoStreamTransformer(onDone: onDone)); + + /// Invokes the given callback function when the stream emits data, emits + /// an error, or emits done. The callback receives a [Notification] object. + /// + /// The [Notification] object contains the [Kind] of event (OnData, onDone, + /// or OnError), and the item or error that was emitted. In the case of + /// onDone, no data is emitted as part of the [Notification]. + /// + /// ### Example + /// + /// Stream.fromIterable([1]) + /// .doOnEach(print) + /// .listen(null); // prints Notification{kind: OnData, value: 1, errorAndStackTrace: null}, Notification{kind: OnDone, value: null, errorAndStackTrace: null} + Stream doOnEach(void onEach(Notification notification)) => + transform(DoStreamTransformer(onEach: onEach)); + + /// Invokes the given callback function when the stream emits an error. + /// + /// ### Example + /// + /// Stream.error(Exception()) + /// .doOnError((error, stacktrace) => print("oh no")) + /// .listen(null); // prints "Oh no" + Stream doOnError(Function onError) => + transform(DoStreamTransformer(onError: onError)); + + /// Invokes the given callback function when the stream is first listened to. + /// + /// ### Example + /// + /// Stream.fromIterable([1]) + /// .doOnListen(() => print("Is someone there?")) + /// .listen(null); // prints "Is someone there?" + Stream doOnListen(void onListen()) => + transform(DoStreamTransformer(onListen: onListen)); + + /// Invokes the given callback function when the stream subscription is + /// paused. + /// + /// ### Example + /// + /// final subscription = Stream.fromIterable([1]) + /// .doOnPause(() => print("Gimme a minute please")) + /// .listen(null); + /// + /// subscription.pause(); // prints "Gimme a minute please" + Stream doOnPause(void onPause(Future resumeSignal)) => + transform(DoStreamTransformer(onPause: onPause)); + + /// Invokes the given callback function when the stream subscription + /// resumes receiving items. + /// + /// ### Example + /// + /// final subscription = Stream.fromIterable([1]) + /// .doOnResume(() => print("Let's do this!")) + /// .listen(null); + /// + /// subscription.pause(); + /// subscription.resume(); "Let's do this!" + Stream doOnResume(void onResume()) => + transform(DoStreamTransformer(onResume: onResume)); +} diff --git a/lib/src/transformers/exhaust_map.dart b/lib/src/transformers/exhaust_map.dart index 1f40af28f..382a73291 100644 --- a/lib/src/transformers/exhaust_map.dart +++ b/lib/src/transformers/exhaust_map.dart @@ -9,10 +9,10 @@ import 'dart:async'; /// /// ### Example /// // Emits 0, 1, 2 -/// new Stream.periodic(new Duration(milliseconds: 200), (i) => i).take(3) -/// .transform(new ExhaustMapStreamTransformer( +/// Stream.periodic(Duration(milliseconds: 200), (i) => i).take(3) +/// .transform(ExhaustMapStreamTransformer( /// // Emits the value it's given after 200ms -/// (i) => new Observable.timer(i, new Duration(milliseconds: 200)), +/// (i) => Observable.timer(i, Duration(milliseconds: 200)), /// )) /// .listen(print); // prints 0, 2 class ExhaustMapStreamTransformer extends StreamTransformerBase { @@ -82,3 +82,24 @@ class ExhaustMapStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to transform the Stream into +/// a new Stream. The new Stream emits items and ignores events from the source +/// Stream until the new Stream completes. +extension ExhaustMapExtension on Stream { + /// Converts items from the source stream into a Stream using a given + /// mapper. It ignores all items from the source stream until the new stream + /// completes. + /// + /// Useful when you have a noisy source Stream and only want to respond once + /// the previous async operation is finished. + /// + /// ### Example + /// + /// RangeStream(0, 2).interval(Duration(milliseconds: 50)) + /// .exhaustMap((i) => + /// TimerStream(i, Duration(milliseconds: 75))) + /// .listen(print); // prints 0, 2 + Stream exhaustMap(Stream mapper(T value)) => + transform(ExhaustMapStreamTransformer(mapper)); +} diff --git a/lib/src/transformers/flat_map.dart b/lib/src/transformers/flat_map.dart index 7b87edb7e..08d911201 100644 --- a/lib/src/transformers/flat_map.dart +++ b/lib/src/transformers/flat_map.dart @@ -10,10 +10,10 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([4, 3, 2, 1]) -/// .transform(new FlatMapStreamTransformer((i) => -/// new Stream.fromFuture( -/// new Future.delayed(new Duration(minutes: i), () => i)) +/// Stream.fromIterable([4, 3, 2, 1]) +/// .transform(FlatMapStreamTransformer((i) => +/// Stream.fromFuture( +/// Future.delayed(Duration(minutes: i), () => i)) /// .listen(print); // prints 1, 2, 3, 4 class FlatMapStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -98,3 +98,41 @@ class FlatMapStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to convert the source Stream into +/// a new Stream each time the source emits an item. +extension FlatMapExtension on Stream { + /// Converts each emitted item into a Stream using the given mapper + /// function. The newly created Stream will be be listened to and begin + /// emitting items downstream. + /// + /// The items emitted by each of the Streams are emitted downstream in the + /// same order they arrive. In other words, the sequences are merged + /// together. + /// + /// ### Example + /// + /// RangeStream(4, 1) + /// .flatMap((i) => + /// TimerStream(i, Duration(minutes: i)) + /// .listen(print); // prints 1, 2, 3, 4 + Stream flatMap(Stream mapper(T value)) => + transform(FlatMapStreamTransformer(mapper)); + + /// Converts each item into a Stream. The Stream must return an + /// Iterable. Then, each item from the Iterable will be emitted one by one. + /// + /// Use case: you may have an API that returns a list of items, such as + /// a Stream>. However, you might want to operate on the individual items + /// rather than the list itself. This is the job of `flatMapIterable`. + /// + /// ### Example + /// + /// RangeStream(1, 4) + /// .flatMapIterable((i) => + /// Stream.fromIterable([[]i]) + /// .listen(print); // prints 1, 2, 3, 4 + Stream flatMapIterable(Stream> mapper(T value)) => + transform(FlatMapStreamTransformer>(mapper)) + .expand((Iterable iterable) => iterable); +} diff --git a/lib/src/transformers/group_by.dart b/lib/src/transformers/group_by.dart index 1e4a7c2f1..c145a48a1 100644 --- a/lib/src/transformers/group_by.dart +++ b/lib/src/transformers/group_by.dart @@ -78,7 +78,7 @@ class GroupByStreamTransformer } /// The [Observable] used by [GroupByStreamTransformer], it contains events -/// that are groupd by a key value. +/// that are grouped by a key value. class GroupByObservable extends Observable { /// The key is the category to which all events in this group belong to. final S key; @@ -87,3 +87,18 @@ class GroupByObservable extends Observable { /// categorized under [key]. GroupByObservable(this.key, Stream stream) : super(stream); } + +/// Extends the Stream class with the ability to convert events into Streams +/// of events that are united by a key. +extension GroupByExtension on Stream { + /// The GroupBy operator divides an [Stream] that emits items into an [Stream] + /// that emits [GroupByStream], each one of which emits some subset of the + /// items from the original source [Stream]. + /// + /// [GroupByStream] acts like a regular [Stream], yet adding a 'key' property, + /// which receives its [Type] and value from the [grouper] Function. + /// + /// All items with the same key are emitted by the same [GroupByStream]. + Stream> groupBy(S grouper(T value)) => + transform(GroupByStreamTransformer(grouper)); +} diff --git a/lib/src/transformers/ignore_elements.dart b/lib/src/transformers/ignore_elements.dart index c1e5cd8a4..fa35feff3 100644 --- a/lib/src/transformers/ignore_elements.dart +++ b/lib/src/transformers/ignore_elements.dart @@ -5,9 +5,9 @@ import 'dart:async'; /// /// ### Example /// -/// new MergeStream([ -/// new Stream.fromIterable([1]), -/// new ErrorStream(new Exception()) +/// MergeStream([ +/// Stream.fromIterable([1]), +/// ErrorStream(Exception()) /// ]) /// .listen(print, onError: print); // prints Exception class IgnoreElementsStreamTransformer extends StreamTransformerBase { @@ -42,3 +42,18 @@ class IgnoreElementsStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to skip, or ignore, data events. +extension IgnoreElementsExtension on Stream { + /// Creates a Stream where all emitted items are ignored, only the error / + /// completed notifications are passed + /// + /// ### Example + /// + /// MergeStream([ + /// Stream.fromIterable([1]), + /// Stream.error(Exception()) + /// ]) + /// .listen(print, onError: print); // prints Exception + Stream ignoreElements() => transform(IgnoreElementsStreamTransformer()); +} diff --git a/lib/src/transformers/interval.dart b/lib/src/transformers/interval.dart index 7931e4d4b..f1c0776be 100644 --- a/lib/src/transformers/interval.dart +++ b/lib/src/transformers/interval.dart @@ -5,8 +5,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([1, 2, 3]) -/// .transform(new IntervalStreamTransformer(Duration(seconds: 1))) +/// Stream.fromIterable([1, 2, 3]) +/// .transform(IntervalStreamTransformer(Duration(seconds: 1))) /// .listen((i) => print("$i sec"); // prints 1 sec, 2 sec, 3 sec class IntervalStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -60,3 +60,18 @@ class IntervalStreamTransformer extends StreamTransformerBase { return controller.stream.listen(null); }); } + +/// Extends the Stream class with the ability to emit each item after a given +/// duration. +extension IntervalExtension on Stream { + /// Creates a Stream that emits each item in the Stream after a given + /// duration. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .interval(Duration(seconds: 1)) + /// .listen((i) => print("$i sec"); // prints 1 sec, 2 sec, 3 sec + Stream interval(Duration duration) => + transform(IntervalStreamTransformer(duration)); +} diff --git a/lib/src/transformers/map_to.dart b/lib/src/transformers/map_to.dart index 03bed853b..c9103ea86 100644 --- a/lib/src/transformers/map_to.dart +++ b/lib/src/transformers/map_to.dart @@ -38,3 +38,17 @@ class MapToStreamTransformer extends StreamTransformerBase { return controller.stream.listen(null); }); } + +/// Extends the Stream class with the ability to convert each item to the same +/// value. +extension MapToExtension on Stream { + /// Emits the given constant value on the output Stream every time the source + /// Stream emits a value. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3, 4]) + /// .mapTo(true) + /// .listen(print); // prints true, true, true, true + Stream mapTo(S value) => transform(MapToStreamTransformer(value)); +} diff --git a/lib/src/transformers/materialize.dart b/lib/src/transformers/materialize.dart index 32310ab51..ef8852f5c 100644 --- a/lib/src/transformers/materialize.dart +++ b/lib/src/transformers/materialize.dart @@ -11,7 +11,7 @@ import 'package:rxdart/src/utils/notification.dart'; /// /// ### Example /// -/// new Stream.fromIterable([1]) +/// Stream.fromIterable([1]) /// .transform(materializeTransformer()) /// .listen((i) => print(i)); // Prints onData & onDone Notification class MaterializeStreamTransformer @@ -62,3 +62,26 @@ class MaterializeStreamTransformer }); } } + +/// Extends the Stream class with the ability to convert the onData, on Done, +/// and onError events into [Notification]s that are passed into the +/// downstream onData listener. +extension MaterializeExtension on Stream { + /// Converts the onData, on Done, and onError events into [Notification] + /// objects that are passed into the downstream onData listener. + /// + /// The [Notification] object contains the [Kind] of event (OnData, onDone, or + /// OnError), and the item or error that was emitted. In the case of onDone, + /// no data is emitted as part of the [Notification]. + /// + /// Example: + /// Stream.fromIterable([1]) + /// .materialize() + /// .listen((i) => print(i)); // Prints onData & onDone Notification + /// + /// Stream.error(Exception()) + /// .materialize() + /// .listen((i) => print(i)); // Prints onError Notification + Stream> materialize() => + transform(MaterializeStreamTransformer()); +} diff --git a/lib/src/transformers/max.dart b/lib/src/transformers/max.dart new file mode 100644 index 000000000..687b0f381 --- /dev/null +++ b/lib/src/transformers/max.dart @@ -0,0 +1,25 @@ + +/// Extends the Stream class with the ability to transform into a Future +/// that completes with the largest item emitted by the Stream. +extension MaxExtension on Stream { + /// Converts a Stream into a Future that completes with the largest item + /// emitted by the Stream. + /// + /// This is similar to finding the max value in a list, but the values are + /// asynchronous. + /// + /// ### Example + /// + /// final max = await Stream.fromIterable([1, 2, 3]).max(); + /// + /// print(max); // prints 3 + /// + /// ### Example with custom [Comparator] + /// + /// final stream = Stream.fromIterable(["short", "looooooong"]); + /// final max = await stream.max((a, b) => a.length - b.length); + /// + /// print(max); // prints "looooooong" + Future max([Comparator comparator]) => + toList().then((List values) => (values..sort(comparator)).last); +} diff --git a/lib/src/transformers/min.dart b/lib/src/transformers/min.dart new file mode 100644 index 000000000..26958c796 --- /dev/null +++ b/lib/src/transformers/min.dart @@ -0,0 +1,24 @@ +/// Extends the Stream class with the ability to transform into a Future +/// that completes with the smallest item emitted by the Stream. +extension MinExtension on Stream { + /// Converts a Stream into a Future that completes with the smallest item + /// emitted by the Stream. + /// + /// This is similar to finding the min value in a list, but the values are + /// asynchronous! + /// + /// ### Example + /// + /// final min = await Stream.fromIterable([1, 2, 3]).min(); + /// + /// print(min); // prints 1 + /// + /// ### Example with custom [Comparator] + /// + /// final stream = Stream.fromIterable(["short", "looooooong"]); + /// final min = await stream.min((a, b) => a.length - b.length); + /// + /// print(min); // prints "short" + Future min([Comparator comparator]) => + toList().then((List values) => (values..sort(comparator)).first); +} diff --git a/lib/src/transformers/of_type.dart b/lib/src/transformers/of_type.dart deleted file mode 100644 index b65350312..000000000 --- a/lib/src/transformers/of_type.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/src/utils/type_token.dart'; - -/// Filters a sequence so that only events of a given type pass -/// -/// In order to capture the Type correctly, it needs to be wrapped -/// in a [TypeToken] as the generic parameter. -/// -/// Given the way Dart generics work, one cannot simply use the `is T` / `as T` -/// checks and castings within `OfTypeStreamTransformer` itself. Therefore, the -/// [TypeToken] class was introduced to capture the type of class you'd -/// like `ofType` to filter down to. -/// -/// ### Examples -/// -/// new Stream.fromIterable([1, "hi"]) -/// .ofType(new TypeToken) -/// .listen(print); // prints "hi" -/// -/// As a shortcut, you can use some pre-defined constants to write the above -/// in the following way: -/// -/// new Stream.fromIterable([1, "hi"]) -/// .transform(new OfTypeStreamTransformer(kString)) -/// .listen(print); // prints "hi" -/// -/// If you'd like to create your own shortcuts like the example above, -/// simply create a constant: -/// -/// const TypeToken> kMapIntString = -/// const TypeToken>(); -@Deprecated('Please use WhereTypeStreamTransformer instead') -class OfTypeStreamTransformer extends StreamTransformerBase { - final StreamTransformer _transformer; - - /// Constructs a [StreamTransformer] which uses a [typeToken] to filter and - /// cast events from the source [Stream] into events of type [S]. - OfTypeStreamTransformer(TypeToken typeToken) - : _transformer = _buildTransformer(typeToken); - - @override - Stream bind(Stream stream) => _transformer.bind(stream); - - static StreamTransformer _buildTransformer( - TypeToken typeToken) { - return StreamTransformer((Stream input, bool cancelOnError) { - StreamController controller; - StreamSubscription subscription; - - controller = StreamController( - sync: true, - onListen: () { - subscription = input.listen((T value) { - try { - if (typeToken.isType(value)) { - controller.add(typeToken.toType(value)); - } - } catch (e, s) { - controller.addError(e, s); - } - }, - onError: controller.addError, - onDone: controller.close, - cancelOnError: cancelOnError); - }, - onPause: ([Future resumeSignal]) => - subscription.pause(resumeSignal), - onResume: () => subscription.resume(), - onCancel: () => subscription.cancel()); - - return controller.stream.listen(null); - }); - } -} diff --git a/lib/src/transformers/on_error_resume.dart b/lib/src/transformers/on_error_resume.dart index 320f309a0..ae8ce2572 100644 --- a/lib/src/transformers/on_error_resume.dart +++ b/lib/src/transformers/on_error_resume.dart @@ -14,9 +14,9 @@ import 'dart:async'; /// /// ### Example /// -/// new Observable.error(new Exception()) +/// Observable.error(Exception()) /// .onErrorResume((dynamic e) => -/// new Observable.just(e is StateError ? 1 : 0) +/// Observable.just(e is StateError ? 1 : 0) /// .listen(print); // prints 0 class OnErrorResumeStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -84,3 +84,137 @@ class OnErrorResumeStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to recover from errors in various +/// ways +extension OnErrorExtensions on Stream { + /// Intercepts error events and switches to the given recovery stream in + /// that case + /// + /// The onErrorResumeNext operator intercepts an onError notification from + /// the source Stream. Instead of passing the error through to any + /// listeners, it replaces it with another Stream of items. + /// + /// If you need to perform logic based on the type of error that was emitted, + /// please consider using [onErrorResume]. + /// + /// ### Example + /// + /// ErrorStream(Exception()) + /// .onErrorResumeNext(Stream.fromIterable([1, 2, 3])) + /// .listen(print); // prints 1, 2, 3 + Stream onErrorResumeNext(Stream recoveryStream) => transform( + OnErrorResumeStreamTransformer((dynamic e) => recoveryStream)); + + /// Intercepts error events and switches to a recovery stream created by the + /// provided [recoveryFn]. + /// + /// The onErrorResume operator intercepts an onError notification from + /// the source Stream. Instead of passing the error through to any + /// listeners, it replaces it with another Stream of items created by the + /// [recoveryFn]. + /// + /// The [recoveryFn] receives the emitted error and returns a Stream. You can + /// perform logic in the [recoveryFn] to return different Streams based on the + /// type of error that was emitted. + /// + /// If you do not need to perform logic based on the type of error that was + /// emitted, please consider using [onErrorResumeNext] or [onErrorReturn]. + /// + /// ### Example + /// + /// ErrorStream(Exception()) + /// .onErrorResume((dynamic e) => + /// Stream.fromIterable([e is StateError ? 1 : 0]) + /// .listen(print); // prints 0 + Stream onErrorResume(Stream Function(dynamic error) recoveryFn) => + transform(OnErrorResumeStreamTransformer(recoveryFn)); + + /// instructs a Stream to emit a particular item when it encounters an + /// error, and then terminate normally + /// + /// The onErrorReturn operator intercepts an onError notification from + /// the source Stream. Instead of passing it through to any observers, it + /// replaces it with a given item, and then terminates normally. + /// + /// If you need to perform logic based on the type of error that was emitted, + /// please consider using [onErrorReturnWith]. + /// + /// ### Example + /// + /// ErrorStream(Exception()) + /// .onErrorReturn(1) + /// .listen(print); // prints 1 + Stream onErrorReturn(T returnValue) => + transform(OnErrorResumeStreamTransformer( + (dynamic e) => Stream.fromIterable([returnValue]))); + + /// instructs a Stream to emit a particular item created by the + /// [returnFn] when it encounters an error, and then terminate normally. + /// + /// The onErrorReturnWith operator intercepts an onError notification from + /// the source Stream. Instead of passing it through to any observers, it + /// replaces it with a given item, and then terminates normally. + /// + /// The [returnFn] receives the emitted error and returns a Stream. You can + /// perform logic in the [returnFn] to return different Streams based on the + /// type of error that was emitted. + /// + /// If you do not need to perform logic based on the type of error that was + /// emitted, please consider using [onErrorReturn]. + /// + /// ### Example + /// + /// ErrorStream(Exception()) + /// .onErrorReturnWith((e) => e is Exception ? 1 : 0) + /// .listen(print); // prints 1 + Stream onErrorReturnWith(T Function(dynamic error) returnFn) => + transform(OnErrorResumeStreamTransformer( + (dynamic e) => Stream.fromIterable([returnFn(e)]))); + + /// Terminates the Stream if an error is emitted. + /// + /// By default, if any error is emitted, the Stream will be terminated. You + /// may provide an optional [shouldClose] predicate function to determine + /// whether or not the Stream should be terminated in response to a specific + /// error. + /// + /// ### Example + /// + /// final stream = ConcatStream([ + /// Stream.fromIterable([1]), + /// Stream.error(Exception()), + /// Stream.fromIterable([2]), + /// ]); + /// + /// stream.onErrorClose().listen(print); // prints 1 + /// + /// ### Predicate Example + /// + /// final stream = ConcatStream([ + /// Stream.fromIterable([1]), + /// Stream.error(Exception()), + /// Stream.fromIterable([2]), + /// Stream.error(StateError("Oh no")), + /// Stream.fromIterable([3]), + /// ]); + /// + /// stream + /// .onErrorClose((dynamic e) => e is StateError) + /// .listen(print); // prints 1, 2 + Stream onErrorClose([ + bool Function(dynamic error) shouldClose = _alwaysClose, + ]) { + return transform(StreamTransformer.fromHandlers( + handleError: (e, s, sink) { + if (shouldClose(e)) { + sink.close(); + } else { + sink.addError(e, s); + } + }, + )); + } + + static bool _alwaysClose(dynamic error) => true; +} diff --git a/lib/src/transformers/scan.dart b/lib/src/transformers/scan.dart index 316ef77b9..4d9008398 100644 --- a/lib/src/transformers/scan.dart +++ b/lib/src/transformers/scan.dart @@ -6,8 +6,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([1, 2, 3]) -/// .transform(new ScanStreamTransformer((acc, curr, i) => acc + curr, 0)) +/// Stream.fromIterable([1, 2, 3]) +/// .transform(ScanStreamTransformer((acc, curr, i) => acc + curr, 0)) /// .listen(print); // prints 1, 3, 6 class ScanStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -56,3 +56,21 @@ class ScanStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends +extension ScanExtension on Stream { + /// Applies an accumulator function over a Stream sequence and returns each + /// intermediate result. The optional seed value is used as the initial + /// accumulator value. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2, 3]) + /// .scan((acc, curr, i) => acc + curr, 0) + /// .listen(print); // prints 1, 3, 6 + Stream scan( + S accumulator(S accumulated, T value, int index), [ + S seed, + ]) => + transform(ScanStreamTransformer(accumulator, seed)); +} diff --git a/lib/src/transformers/skip_until.dart b/lib/src/transformers/skip_until.dart index 892c21109..15a4c6b37 100644 --- a/lib/src/transformers/skip_until.dart +++ b/lib/src/transformers/skip_until.dart @@ -4,11 +4,11 @@ import 'dart:async'; /// /// ### Example /// -/// new MergeStream([ -/// new Observable.just(1), -/// new TimerStream(2, new Duration(minutes: 2)) +/// MergeStream([ +/// Observable.just(1), +/// TimerStream(2, Duration(minutes: 2)) /// ]) -/// .transform(skipUntilTransformer(new TimerStream(1, new Duration(minutes: 1)))) +/// .transform(skipUntilTransformer(TimerStream(1, Duration(minutes: 1)))) /// .listen(print); // prints 2; class SkipUntilStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -72,3 +72,20 @@ class SkipUntilStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to skip events until another +/// Stream emits an item. +extension SkipUntilExtension on Stream { + /// Starts emitting items only after the given stream emits an item. + /// + /// ### Example + /// + /// MergeStream([ + /// Stream.fromIterable([1]), + /// TimerStream(2, Duration(minutes: 2)) + /// ]) + /// .skipUntil(TimerStream(true, Duration(minutes: 1))) + /// .listen(print); // prints 2; + Stream skipUntil(Stream otherStream) => + transform(SkipUntilStreamTransformer(otherStream)); +} diff --git a/lib/src/transformers/start_with.dart b/lib/src/transformers/start_with.dart index f5aa726cc..f79250c41 100644 --- a/lib/src/transformers/start_with.dart +++ b/lib/src/transformers/start_with.dart @@ -4,8 +4,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([2]) -/// .transform(new StartWithStreamTransformer(1)) +/// Stream.fromIterable([2]) +/// .transform(StartWithStreamTransformer(1)) /// .listen(print); // prints 1, 2 class StartWithStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -46,3 +46,15 @@ class StartWithStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to emit the given value as the +/// first item. +extension StartWithExtension on Stream { + /// Prepends a value to the source Stream. + /// + /// ### Example + /// + /// Stream.fromIterable([2]).startWith(1).listen(print); // prints 1, 2 + Stream startWith(T startValue) => + transform(StartWithStreamTransformer(startValue)); +} diff --git a/lib/src/transformers/start_with_error.dart b/lib/src/transformers/start_with_error.dart index 7587a3e9f..f1bfdd62b 100644 --- a/lib/src/transformers/start_with_error.dart +++ b/lib/src/transformers/start_with_error.dart @@ -4,8 +4,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([2]) -/// .transform(new StartWithErrorStreamTransformer('error')) +/// Stream.fromIterable([2]) +/// .transform(StartWithErrorStreamTransformer('error')) /// .listen(null, onError: (e) => print(e)); // prints 'error' class StartWithErrorStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; diff --git a/lib/src/transformers/start_with_many.dart b/lib/src/transformers/start_with_many.dart index bc60dd430..05a54d565 100644 --- a/lib/src/transformers/start_with_many.dart +++ b/lib/src/transformers/start_with_many.dart @@ -4,8 +4,8 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([3]) -/// .transform(new StartWithManyStreamTransformer([1, 2])) +/// Stream.fromIterable([3]) +/// .transform(StartWithManyStreamTransformer([1, 2])) /// .listen(print); // prints 1, 2, 3 class StartWithManyStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -48,3 +48,16 @@ class StartWithManyStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability to emit the given values as the +/// first items. +extension StartWithManyExtension on Stream { + /// Prepends a sequence of values to the source Stream. + /// + /// ### Example + /// + /// Stream.fromIterable([3]).startWithMany([1, 2]) + /// .listen(print); // prints 1, 2, 3 + Stream startWithMany(List startValues) => + transform(StartWithManyStreamTransformer(startValues)); +} diff --git a/lib/src/transformers/switch_if_empty.dart b/lib/src/transformers/switch_if_empty.dart index 308d41ccb..c54cd4c42 100644 --- a/lib/src/transformers/switch_if_empty.dart +++ b/lib/src/transformers/switch_if_empty.dart @@ -91,3 +91,34 @@ class SwitchIfEmptyStreamTransformer extends StreamTransformerBase { }); } } + +/// Extend the Stream class with the ability to return an alternative Stream +/// if the initial Stream completes with no items. +extension SwitchIfEmptyExtension on Stream { + /// When the original Stream emits no items, this operator subscribes to the + /// given fallback stream and emits items from that Stream instead. + /// + /// This can be particularly useful when consuming data from multiple sources. + /// For example, when using the Repository Pattern. Assuming you have some + /// data you need to load, you might want to start with the fastest access + /// point and keep falling back to the slowest point. For example, first query + /// an in-memory database, then a database on the file system, then a network + /// call if the data isn't on the local machine. + /// + /// This can be achieved quite simply with switchIfEmpty! + /// + /// ### Example + /// + /// // Let's pretend we have some Data sources that complete without + /// // emitting any items if they don't contain the data we're looking for + /// Stream memory; + /// Stream disk; + /// Stream network; + /// + /// // Start with memory, fallback to disk, then fallback to network. + /// // Simple as that! + /// Stream getThatData = + /// memory.switchIfEmpty(disk).switchIfEmpty(network); + Stream switchIfEmpty(Stream fallbackStream) => + transform(SwitchIfEmptyStreamTransformer(fallbackStream)); +} diff --git a/lib/src/transformers/switch_map.dart b/lib/src/transformers/switch_map.dart index c4860af79..bf96644be 100644 --- a/lib/src/transformers/switch_map.dart +++ b/lib/src/transformers/switch_map.dart @@ -12,10 +12,10 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([4, 3, 2, 1]) -/// .transform(new SwitchMapStreamTransformer((i) => -/// new Stream.fromFuture( -/// new Future.delayed(new Duration(minutes: i), () => i)) +/// Stream.fromIterable([4, 3, 2, 1]) +/// .transform(SwitchMapStreamTransformer((i) => +/// Stream.fromFuture( +/// Future.delayed(Duration(minutes: i), () => i)) /// .listen(print); // prints 1 class SwitchMapStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -88,3 +88,27 @@ class SwitchMapStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream with the ability to convert one stream into a new Stream +/// whenever the source emits an item. Every time a new Stream is created, the +/// previous Stream is discarded. +extension SwitchMapExtension on Stream { + /// Converts each emitted item into a Stream using the given mapper function. + /// The newly created Stream will be be listened to and begin emitting items, + /// and any previously created Stream will stop emitting. + /// + /// The switchMap operator is similar to the flatMap and concatMap methods, + /// but it only emits items from the most recently created Stream. + /// + /// This can be useful when you only want the very latest state from + /// asynchronous APIs, for example. + /// + /// ### Example + /// + /// RangeStream(4, 1) + /// .switchMap((i) => + /// TimerStream(i, Duration(minutes: i)) + /// .listen(print); // prints 1 + Stream switchMap(Stream mapper(T value)) => + transform(SwitchMapStreamTransformer(mapper)); +} diff --git a/lib/src/transformers/take_until.dart b/lib/src/transformers/take_until.dart index a06bd0f0c..c5f2d570a 100644 --- a/lib/src/transformers/take_until.dart +++ b/lib/src/transformers/take_until.dart @@ -5,12 +5,12 @@ import 'dart:async'; /// /// ### Example /// -/// new MergeStream([ -/// new Stream.fromIterable([1]), -/// new TimerStream(2, new Duration(minutes: 1)) +/// MergeStream([ +/// Stream.fromIterable([1]), +/// TimerStream(2, Duration(minutes: 1)) /// ]) -/// .transform(new TakeUntilStreamTransformer( -/// new TimerStream(3, new Duration(seconds: 10)))) +/// .transform(TakeUntilStreamTransformer( +/// TimerStream(3, Duration(seconds: 10)))) /// .listen(print); // prints 1 class TakeUntilStreamTransformer extends StreamTransformerBase { final StreamTransformer _transformer; @@ -64,3 +64,21 @@ class TakeUntilStreamTransformer extends StreamTransformerBase { }); } } + +/// Extends the Stream class with the ability receive events from the source +/// Stream until another Stream produces a value. +extension TakeUntilExtension on Stream { + /// Returns the values from the source Stream sequence until the other Stream + /// sequence produces a value. + /// + /// ### Example + /// + /// MergeStream([ + /// Stream.fromIterable([1]), + /// TimerStream(2, Duration(minutes: 1)) + /// ]) + /// .takeUntil(TimerStream(3, Duration(seconds: 10))) + /// .listen(print); // prints 1 + Stream takeUntil(Stream otherStream) => + transform(TakeUntilStreamTransformer(otherStream)); +} diff --git a/lib/src/transformers/time_interval.dart b/lib/src/transformers/time_interval.dart index c804e6258..7c09da12f 100644 --- a/lib/src/transformers/time_interval.dart +++ b/lib/src/transformers/time_interval.dart @@ -5,9 +5,9 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([1]) -/// .transform(new IntervalStreamTransformer(new Duration(seconds: 1))) -/// .transform(new TimeIntervalStreamTransformer()) +/// Stream.fromIterable([1]) +/// .transform(IntervalStreamTransformer(Duration(seconds: 1))) +/// .transform(TimeIntervalStreamTransformer()) /// .listen(print); // prints TimeInterval{interval: 0:00:01, value: 1} class TimeIntervalStreamTransformer extends StreamTransformerBase> { @@ -97,3 +97,19 @@ class TimeInterval { return 'TimeInterval{interval: $interval, value: $value}'; } } + +/// Extends the Stream class with the ability to wrap each item emitted by the +/// source Observable in a [Timestamped] object that includes the emitted item +/// and the time when the item was emitted. +extension TimeIntervalExtension on Stream { + /// Records the time interval between consecutive values in a Stream sequence. + /// + /// ### Example + /// + /// Stream.fromIterable([1]) + /// .interval(Duration(seconds: 1)) + /// .timeInterval() + /// .listen(print); // prints TimeInterval{interval: 0:00:01, value: 1} + Stream> timeInterval() => + transform(TimeIntervalStreamTransformer()); +} diff --git a/lib/src/transformers/timestamp.dart b/lib/src/transformers/timestamp.dart index 52dc8df62..60c828584 100644 --- a/lib/src/transformers/timestamp.dart +++ b/lib/src/transformers/timestamp.dart @@ -5,8 +5,8 @@ import 'dart:async'; /// /// Example /// -/// new Stream.fromIterable([1]) -/// .transform(new TimestampStreamTransformer()) +/// Stream.fromIterable([1]) +/// .transform(TimestampStreamTransformer()) /// .listen((i) => print(i)); // prints 'TimeStamp{timestamp: XXX, value: 1}'; class TimestampStreamTransformer extends StreamTransformerBase> { @@ -78,3 +78,17 @@ class Timestamped { return 'TimeStamp{timestamp: $timestamp, value: $value}'; } } + +/// Extends the Stream class with the ability to +extension TimeStampExtension on Stream { + /// Wraps each item emitted by the source Stream in a [Timestamped] object + /// that includes the emitted item and the time when the item was emitted. + /// + /// Example + /// + /// Stream.fromIterable([1]) + /// .timestamp() + /// .listen((i) => print(i)); // prints 'TimeStamp{timestamp: XXX, value: 1}'; + Stream> timestamp() => + transform(TimestampStreamTransformer()); +} diff --git a/lib/src/transformers/where_type.dart b/lib/src/transformers/where_type.dart index 23c566656..86373c196 100644 --- a/lib/src/transformers/where_type.dart +++ b/lib/src/transformers/where_type.dart @@ -56,3 +56,27 @@ class WhereTypeStreamTransformer extends StreamTransformerBase { return controller.stream.listen(null); }); } + +/// Extends the Stream class with the ability to filter down events to only +/// those of a specific type. +extension WhereTypeExtension on Stream { + /// This transformer is a shorthand for [Stream.where] followed by + /// [Stream.cast]. + /// + /// Events that do not match [T] are filtered out, the resulting [Stream] will + /// be of Type [T]. + /// + /// ### Example + /// + /// Stream.fromIterable([1, 'two', 3, 'four']) + /// .whereType() + /// .listen(print); // prints 1, 3 + /// + /// #### as opposed to: + /// + /// Stream.fromIterable([1, 'two', 3, 'four']) + /// .where((event) => event is int) + /// .cast() + /// .listen(print); // prints 1, 3 + Stream whereType() => transform(WhereTypeStreamTransformer()); +} diff --git a/lib/src/transformers/with_latest_from.dart b/lib/src/transformers/with_latest_from.dart index 1513c1b6e..7d4c7fd06 100644 --- a/lib/src/transformers/with_latest_from.dart +++ b/lib/src/transformers/with_latest_from.dart @@ -10,9 +10,9 @@ import 'dart:async'; /// /// ### Example /// -/// new Stream.fromIterable([1, 2]).transform( -/// new WithLatestFromStreamTransformer( -/// new Stream.fromIterable([2, 3]), (a, b) => a + b) +/// Stream.fromIterable([1, 2]).transform( +/// WithLatestFromStreamTransformer( +/// Stream.fromIterable([2, 3]), (a, b) => a + b) /// .listen(print); // prints 4 (due to the async nature of streams) class WithLatestFromStreamTransformer extends StreamTransformerBase { @@ -411,3 +411,358 @@ class WithLatestFromStreamTransformer }); } } + +/// Extends the Stream class with the ability to merge the source Stream with +/// the last emitted item from another Stream. +extension WithLatestFromExtensions on Stream { + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the two streams using the provided function. + /// + /// If the latestFromStream has not emitted any values, this stream will not + /// emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]).withLatestFrom( + /// Stream.fromIterable([2, 3]), (a, b) => a + b) + /// .listen(print); // prints 4 (due to the async nature of streams) + Stream withLatestFrom(Stream latestFromStream, R fn(T t, S s)) => + transform(WithLatestFromStreamTransformer.with1(latestFromStream, fn)); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the streams into a list. This is helpful when you need + /// to combine a dynamic number of Streams. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// Stream.fromIterable([1, 2]).withLatestFromList( + /// [ + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// Stream.fromIterable([6, 7]), + /// ], + /// ).listen(print); // print [2, 2, 3, 4, 5, 6] (due to the async nature of streams) + /// + Stream> withLatestFromList(Iterable> latestFromStreams) => + transform(WithLatestFromStreamTransformer.withList(latestFromStreams)); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the three streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom2( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// (int a, int b, int c) => a + b + c, + /// ) + /// .listen(print); // prints 7 (due to the async nature of streams) + Stream withLatestFrom2( + Stream latestFromStream1, + Stream latestFromStream2, + R fn(T t, A a, B b), + ) => + transform(WithLatestFromStreamTransformer.with2( + latestFromStream1, + latestFromStream2, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the four streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom3( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// (int a, int b, int c, int d) => a + b + c + d, + /// ) + /// .listen(print); // prints 11 (due to the async nature of streams) + Stream withLatestFrom3( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + R fn(T t, A a, B b, C c), + ) => + transform(WithLatestFromStreamTransformer.with3( + latestFromStream1, + latestFromStream2, + latestFromStream3, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the five streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom4( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// (int a, int b, int c, int d, int e) => a + b + c + d + e, + /// ) + /// .listen(print); // prints 16 (due to the async nature of streams) + Stream withLatestFrom4( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + Stream latestFromStream4, + R fn(T t, A a, B b, C c, D d), + ) => + transform(WithLatestFromStreamTransformer.with4( + latestFromStream1, + latestFromStream2, + latestFromStream3, + latestFromStream4, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the six streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom5( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// Stream.fromIterable([6, 7]), + /// (int a, int b, int c, int d, int e, int f) => a + b + c + d + e + f, + /// ) + /// .listen(print); // prints 22 (due to the async nature of streams) + Stream withLatestFrom5( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + Stream latestFromStream4, + Stream latestFromStream5, + R fn(T t, A a, B b, C c, D d, E e), + ) => + transform(WithLatestFromStreamTransformer.with5( + latestFromStream1, + latestFromStream2, + latestFromStream3, + latestFromStream4, + latestFromStream5, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the seven streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom6( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// Stream.fromIterable([6, 7]), + /// Stream.fromIterable([7, 8]), + /// (int a, int b, int c, int d, int e, int f, int g) => + /// a + b + c + d + e + f + g, + /// ) + /// .listen(print); // prints 29 (due to the async nature of streams) + Stream withLatestFrom6( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + Stream latestFromStream4, + Stream latestFromStream5, + Stream latestFromStream6, + R fn(T t, A a, B b, C c, D d, E e, F f), + ) => + transform(WithLatestFromStreamTransformer.with6( + latestFromStream1, + latestFromStream2, + latestFromStream3, + latestFromStream4, + latestFromStream5, + latestFromStream6, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the eight streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom7( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// Stream.fromIterable([6, 7]), + /// Stream.fromIterable([7, 8]), + /// Stream.fromIterable([8, 9]), + /// (int a, int b, int c, int d, int e, int f, int g, int h) => + /// a + b + c + d + e + f + g + h, + /// ) + /// .listen(print); // prints 37 (due to the async nature of streams) + Stream withLatestFrom7( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + Stream latestFromStream4, + Stream latestFromStream5, + Stream latestFromStream6, + Stream latestFromStream7, + R fn(T t, A a, B b, C c, D d, E e, F f, G g), + ) => + transform(WithLatestFromStreamTransformer.with7( + latestFromStream1, + latestFromStream2, + latestFromStream3, + latestFromStream4, + latestFromStream5, + latestFromStream6, + latestFromStream7, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the nine streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom8( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// Stream.fromIterable([6, 7]), + /// Stream.fromIterable([7, 8]), + /// Stream.fromIterable([8, 9]), + /// Stream.fromIterable([9, 10]), + /// (int a, int b, int c, int d, int e, int f, int g, int h, int i) => + /// a + b + c + d + e + f + g + h + i, + /// ) + /// .listen(print); // prints 46 (due to the async nature of streams) + Stream withLatestFrom8( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + Stream latestFromStream4, + Stream latestFromStream5, + Stream latestFromStream6, + Stream latestFromStream7, + Stream latestFromStream8, + R fn(T t, A a, B b, C c, D d, E e, F f, G g, H h), + ) => + transform(WithLatestFromStreamTransformer.with8( + latestFromStream1, + latestFromStream2, + latestFromStream3, + latestFromStream4, + latestFromStream5, + latestFromStream6, + latestFromStream7, + latestFromStream8, + fn, + )); + + /// Creates a Stream that emits when the source stream emits, combining the + /// latest values from the ten streams using the provided function. + /// + /// If any of latestFromStreams has not emitted any values, this stream will + /// not emit either. + /// + /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) + /// + /// ### Example + /// + /// Stream.fromIterable([1, 2]) + /// .withLatestFrom9( + /// Stream.fromIterable([2, 3]), + /// Stream.fromIterable([3, 4]), + /// Stream.fromIterable([4, 5]), + /// Stream.fromIterable([5, 6]), + /// Stream.fromIterable([6, 7]), + /// Stream.fromIterable([7, 8]), + /// Stream.fromIterable([8, 9]), + /// Stream.fromIterable([9, 10]), + /// Stream.fromIterable([10, 11]), + /// (int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) => + /// a + b + c + d + e + f + g + h + i + j, + /// ) + /// .listen(print); // prints 46 (due to the async nature of streams) + Stream withLatestFrom9( + Stream latestFromStream1, + Stream latestFromStream2, + Stream latestFromStream3, + Stream latestFromStream4, + Stream latestFromStream5, + Stream latestFromStream6, + Stream latestFromStream7, + Stream latestFromStream8, + Stream latestFromStream9, + R fn(T t, A a, B b, C c, D d, E e, F f, G g, H h, I i), + ) => + transform(WithLatestFromStreamTransformer.with9( + latestFromStream1, + latestFromStream2, + latestFromStream3, + latestFromStream4, + latestFromStream5, + latestFromStream6, + latestFromStream7, + latestFromStream8, + latestFromStream9, + fn, + )); +} diff --git a/lib/src/utils/type_token.dart b/lib/src/utils/type_token.dart deleted file mode 100644 index 5cd34141a..000000000 --- a/lib/src/utils/type_token.dart +++ /dev/null @@ -1,52 +0,0 @@ -/// A class that captures the Type to filter down to using `ofType` or `cast`. -/// -/// Given the way Dart generics work, one cannot simply use the `is T` / `as T` -/// checks and castings within an ofTypeObservable itself. Therefore, this class -/// was introduced to capture the type of class you'd like `ofType` to filter -/// down to, or `cast` to cast to. -/// -/// ### Example -/// -/// new Stream.fromIterable([1, "hi"]) -/// .ofType(new TypeToken) -/// .listen(print); // prints "hi" -@Deprecated('Please use Observable.whereType instead') -class TypeToken { - /// Constructs a token which is used to filter and cast the events of an [Observable]. - const TypeToken(); - - /// A test to determine whether the provided object is of type [S]. - bool isType(dynamic other) { - return other is S; - } - - /// Casts an object to be of type [S]. - S toType(dynamic other) { - // ignore: avoid_as - return other as S; - } -} - -/// Filter the observable to only Booleans -// ignore: deprecated_member_use_from_same_package -const TypeToken kBool = TypeToken(); - -/// Filter the observable to only Doubles -// ignore: deprecated_member_use_from_same_package -const TypeToken kDouble = TypeToken(); - -/// Filter the observable to only Integers -// ignore: deprecated_member_use_from_same_package -const TypeToken kInt = TypeToken(); - -/// Filter the observable to only Numbers -// ignore: deprecated_member_use_from_same_package -const TypeToken kNum = TypeToken(); - -/// Filter the observable to only Strings -// ignore: deprecated_member_use_from_same_package -const TypeToken kString = TypeToken(); - -/// Filter the observable to only Symbols -// ignore: deprecated_member_use_from_same_package -const TypeToken kSymbol = TypeToken(); diff --git a/lib/streams.dart b/lib/streams.dart index ac22f6ddd..3fefcf5ab 100644 --- a/lib/streams.dart +++ b/lib/streams.dart @@ -1,20 +1,20 @@ library rx_streams; -export 'package:rxdart/src/streams/combine_latest.dart'; -export 'package:rxdart/src/streams/concat.dart'; -export 'package:rxdart/src/streams/concat_eager.dart'; -export 'package:rxdart/src/streams/defer.dart'; -export 'package:rxdart/src/streams/error.dart'; -export 'package:rxdart/src/streams/fork_join.dart'; -export 'package:rxdart/src/streams/merge.dart'; -export 'package:rxdart/src/streams/never.dart'; -export 'package:rxdart/src/streams/race.dart'; -export 'package:rxdart/src/streams/range.dart'; -export 'package:rxdart/src/streams/repeat.dart'; -export 'package:rxdart/src/streams/retry.dart'; -export 'package:rxdart/src/streams/retry_when.dart'; -export 'package:rxdart/src/streams/sequence_equal.dart'; -export 'package:rxdart/src/streams/switch_latest.dart'; -export 'package:rxdart/src/streams/timer.dart'; -export 'package:rxdart/src/streams/utils.dart'; -export 'package:rxdart/src/streams/zip.dart'; +export 'src/streams/combine_latest.dart'; +export 'src/streams/concat.dart'; +export 'src/streams/concat_eager.dart'; +export 'src/streams/defer.dart'; +export 'src/streams/error.dart'; +export 'src/streams/fork_join.dart'; +export 'src/streams/merge.dart'; +export 'src/streams/never.dart'; +export 'src/streams/race.dart'; +export 'src/streams/range.dart'; +export 'src/streams/repeat.dart'; +export 'src/streams/retry.dart'; +export 'src/streams/retry_when.dart'; +export 'src/streams/sequence_equal.dart'; +export 'src/streams/switch_latest.dart'; +export 'src/streams/timer.dart'; +export 'src/streams/utils.dart'; +export 'src/streams/zip.dart'; diff --git a/lib/subjects.dart b/lib/subjects.dart index 75af4812d..fa0f77a20 100644 --- a/lib/subjects.dart +++ b/lib/subjects.dart @@ -1,6 +1,6 @@ library rx_subjects; -export 'package:rxdart/src/subjects/subject.dart'; -export 'package:rxdart/src/subjects/behavior_subject.dart'; -export 'package:rxdart/src/subjects/publish_subject.dart'; -export 'package:rxdart/src/subjects/replay_subject.dart'; +export 'src/subjects/subject.dart'; +export 'src/subjects/behavior_subject.dart'; +export 'src/subjects/publish_subject.dart'; +export 'src/subjects/replay_subject.dart'; diff --git a/lib/transformers.dart b/lib/transformers.dart index 349b654ac..3454dcdc0 100644 --- a/lib/transformers.dart +++ b/lib/transformers.dart @@ -1,36 +1,36 @@ library rx_transformers; -export 'package:rxdart/src/transformers/default_if_empty.dart'; -export 'package:rxdart/src/transformers/delay.dart'; -export 'package:rxdart/src/transformers/dematerialize.dart'; -export 'package:rxdart/src/transformers/distinct_unique.dart'; -export 'package:rxdart/src/transformers/do.dart'; -export 'package:rxdart/src/transformers/exhaust_map.dart'; -export 'package:rxdart/src/transformers/flat_map.dart'; -export 'package:rxdart/src/transformers/group_by.dart'; -export 'package:rxdart/src/transformers/ignore_elements.dart'; -export 'package:rxdart/src/transformers/interval.dart'; -export 'package:rxdart/src/transformers/map_to.dart'; -export 'package:rxdart/src/transformers/materialize.dart'; -export 'package:rxdart/src/transformers/of_type.dart'; -export 'package:rxdart/src/transformers/on_error_resume.dart'; -export 'package:rxdart/src/transformers/scan.dart'; -export 'package:rxdart/src/transformers/skip_until.dart'; -export 'package:rxdart/src/transformers/start_with.dart'; -export 'package:rxdart/src/transformers/start_with_many.dart'; -export 'package:rxdart/src/transformers/switch_if_empty.dart'; -export 'package:rxdart/src/transformers/switch_map.dart'; -export 'package:rxdart/src/transformers/take_until.dart'; -export 'package:rxdart/src/transformers/time_interval.dart'; -export 'package:rxdart/src/transformers/timestamp.dart'; -export 'package:rxdart/src/transformers/where_type.dart'; -export 'package:rxdart/src/transformers/with_latest_from.dart'; -export 'package:rxdart/src/utils/notification.dart'; -export 'package:rxdart/src/utils/type_token.dart'; +export 'src/transformers/default_if_empty.dart'; +export 'src/transformers/delay.dart'; +export 'src/transformers/dematerialize.dart'; +export 'src/transformers/distinct_unique.dart'; +export 'src/transformers/do.dart'; +export 'src/transformers/exhaust_map.dart'; +export 'src/transformers/flat_map.dart'; +export 'src/transformers/group_by.dart'; +export 'src/transformers/ignore_elements.dart'; +export 'src/transformers/interval.dart'; +export 'src/transformers/map_to.dart'; +export 'src/transformers/materialize.dart'; +export 'src/transformers/on_error_resume.dart'; +export 'src/transformers/min.dart'; +export 'src/transformers/max.dart'; +export 'src/transformers/scan.dart'; +export 'src/transformers/skip_until.dart'; +export 'src/transformers/start_with.dart'; +export 'src/transformers/start_with_many.dart'; +export 'src/transformers/switch_if_empty.dart'; +export 'src/transformers/switch_map.dart'; +export 'src/transformers/take_until.dart'; +export 'src/transformers/time_interval.dart'; +export 'src/transformers/timestamp.dart'; +export 'src/transformers/where_type.dart'; +export 'src/transformers/with_latest_from.dart'; +export 'src/utils/notification.dart'; -export 'package:rxdart/src/transformers/backpressure/buffer.dart'; -export 'package:rxdart/src/transformers/backpressure/debounce.dart'; -export 'package:rxdart/src/transformers/backpressure/pairwise.dart'; -export 'package:rxdart/src/transformers/backpressure/sample.dart'; -export 'package:rxdart/src/transformers/backpressure/throttle.dart'; -export 'package:rxdart/src/transformers/backpressure/window.dart'; +export 'src/transformers/backpressure/buffer.dart'; +export 'src/transformers/backpressure/debounce.dart'; +export 'src/transformers/backpressure/pairwise.dart'; +export 'src/transformers/backpressure/sample.dart'; +export 'src/transformers/backpressure/throttle.dart'; +export 'src/transformers/backpressure/window.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index cc55a1187..9883e5ce3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: rxdart -version: 0.22.6 +version: 0.23.0-dev.1 authors: - Frank Pepermans - Brian Egan @@ -9,7 +9,7 @@ description: > homepage: https://github.com/ReactiveX/rxdart environment: - sdk: '>=2.0.0-dev <3.0.0' + sdk: '>=2.6.0 <3.0.0' dev_dependencies: build_runner: ^0.9.0 diff --git a/test/futures/as_observable_future_test.dart b/test/futures/as_observable_future_test.dart deleted file mode 100644 index 504b1a4b6..000000000 --- a/test/futures/as_observable_future_test.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/src/futures/as_observable_future.dart'; -import 'package:test/test.dart'; - -void main() { - test('AsObservableFuture.asObservable', () async { - final future = AsObservableFuture(Future.value(1)); - - await expectLater(future.asObservable(), emits(1)); - }); -} diff --git a/test/futures/stream_max_test.dart b/test/futures/stream_max_test.dart deleted file mode 100644 index 490f892f2..000000000 --- a/test/futures/stream_max_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/rxdart.dart'; -import 'package:rxdart/src/futures/as_observable_future.dart'; -import 'package:rxdart/src/futures/stream_max_future.dart'; -import 'package:test/test.dart'; - -void main() { - test('rx.Observable.max', () async { - await expectLater(Observable(_getStream()).max(), completion(9)); - }); - - test('rx.Observable.max.with.comparator', () async { - await expectLater( - Observable.fromIterable(["one", "two", "three"]) - .max((String a, String b) => a.length - b.length), - completion("three")); - }); - - test('returns an AsObservableFuture', () async { - await expectLater( - Observable.fromIterable(["one", "two", "three"]) - .max((String a, String b) => a.length - b.length), - TypeMatcher>()); - }); - - group('MaxFuture', () { - test('emits the maximum value from a list without a comparator', () async { - await expectLater(StreamMaxFuture(_getStream()), completion(9)); - }); - - test('emits the maximum value from a list with a comparator', () async { - final stream = Stream.fromIterable(const ["one", "two", "three"]); - - final stringLengthComparator = - (String a, String b) => a.length - b.length; - - await expectLater(StreamMaxFuture(stream, stringLengthComparator), - completion("three")); - }); - - test('throws the exception when encountered in the stream', () async { - final Stream stream = ConcatStream(>[ - Stream.fromIterable([1]), - ErrorStream(Exception()) - ]); - - await expectLater(StreamMaxFuture(stream), throwsException); - }); - - test('rx.Observable.max.error.comparator', () async { - final stream = - Stream.fromIterable([ErrorComparator(), ErrorComparator()]); - - await expectLater( - StreamMaxFuture(stream), throwsException); - }); - }); -} - -class ErrorComparator implements Comparable { - @override - int compareTo(ErrorComparator other) { - throw Exception(); - } -} - -Stream _getStream() => - Stream.fromIterable(const [2, 3, 3, 5, 2, 9, 1, 2, 0]); diff --git a/test/futures/stream_min_test.dart b/test/futures/stream_min_test.dart deleted file mode 100644 index eddc3b4a2..000000000 --- a/test/futures/stream_min_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/rxdart.dart'; -import 'package:rxdart/src/futures/as_observable_future.dart'; -import 'package:rxdart/src/futures/stream_min_future.dart'; -import 'package:test/test.dart'; - -void main() { - test('rx.Observable.min', () async { - await expectLater(Observable(_getStream()).min(), completion(0)); - }); - - test('rx.Observable.min.with.comparator', () async { - await expectLater( - Observable.fromIterable(["one", "two", "three"]) - .min((String a, String b) => a.length - b.length), - completion("one")); - }); - - test('returns an AsObservableFuture', () async { - await expectLater( - Observable.fromIterable(["one", "two", "three"]) - .min((String a, String b) => a.length - b.length), - TypeMatcher>()); - }); - - group('MinFuture', () { - test('emits the minimum value from a list without a comparator', () async { - await expectLater(StreamMinFuture(_getStream()), completion(0)); - }); - - test('emits the minimum value from a list with a comparator', () async { - final stream = Stream.fromIterable(const ["one", "two", "three"]); - - final stringLengthComparator = - (String a, String b) => a.length - b.length; - - await expectLater(StreamMinFuture(stream, stringLengthComparator), - completion("one")); - }); - - test('throws the exception when encountered in the stream', () async { - final Stream stream = ConcatStream(>[ - Stream.fromIterable([1]), - ErrorStream(Exception()) - ]); - - await expectLater(StreamMinFuture(stream), throwsException); - }); - - test('rx.Observable.min.error.comparator', () async { - final stream = - Stream.fromIterable([ErrorComparator(), ErrorComparator()]); - - await expectLater( - StreamMinFuture(stream), throwsException); - }); - }); -} - -class ErrorComparator implements Comparable { - @override - int compareTo(ErrorComparator other) { - throw Exception(); - } -} - -Stream _getStream() => - Stream.fromIterable(const [2, 3, 3, 5, 2, 9, 1, 2, 0]); diff --git a/test/futures/wrapped_future_test.dart b/test/futures/wrapped_future_test.dart deleted file mode 100644 index cd723d38b..000000000 --- a/test/futures/wrapped_future_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/src/futures/as_observable_future.dart'; -import 'package:test/test.dart'; - -void main() { - test('AsObservableFuture.asObservable', () async { - final future = AsObservableFuture(Future.value(1)); - - await expectLater(future.asObservable(), emits(1)); - }); - - group('WrappedFuture', () { - test('can be converted to a stream', () async { - final future = AsObservableFuture(Future.value(1)); - - await expectLater(future.asStream(), emits(1)); - }); - - test('properly handles catchError', () async { - var catchErrorCalled = false; - - await AsObservableFuture(Future.error(Exception())) - .catchError((dynamic e, dynamic s) { - catchErrorCalled = true; - }); - - await expectLater(catchErrorCalled, isTrue); - }); - - test('handles then', () async { - var thenCalled = false; - - await AsObservableFuture(Future.value(1)).then((int i) { - thenCalled = true; - }); - - await expectLater(thenCalled, isTrue); - }); - - test('handles timeout', () async { - await expectLater( - AsObservableFuture(Future.delayed(Duration(minutes: 1))) - .timeout(Duration(milliseconds: 1)), - throwsA(TypeMatcher())); - }); - - test('handles whenComplete callbacks', () async { - var whenCompleteCalled = false; - - await AsObservableFuture(Future.value(1)).whenComplete(() { - whenCompleteCalled = true; - }); - - await expectLater(whenCompleteCalled, isTrue); - }); - }); -} diff --git a/test/rxdart_test.dart b/test/rxdart_test.dart index 217223bfa..3f0934fc7 100644 --- a/test/rxdart_test.dart +++ b/test/rxdart_test.dart @@ -1,9 +1,5 @@ library test.rx; -import 'futures/as_observable_future_test.dart' as as_observable_future_test; -import 'futures/stream_max_test.dart' as stream_max_test; -import 'futures/stream_min_test.dart' as stream_min_test; -import 'futures/wrapped_future_test.dart' as wrapped_future_test; import 'observables/composite_subscription_test.dart' as composite_subscription_test; import 'observables/publish_connectable_observable_test.dart' @@ -31,8 +27,8 @@ import 'streams/range_test.dart' as range_test; import 'streams/repeat_test.dart' as repeat_test; import 'streams/retry_test.dart' as retry_test; import 'streams/retry_when_test.dart' as retry_when_test; -import 'streams/stream_test.dart' as stream_test; import 'streams/sequence_equals_test.dart' as sequence_equals_test; +import 'streams/stream_test.dart' as stream_test; import 'streams/switch_latest_test.dart' as switch_latest_test; import 'streams/timer_test.dart' as timer_test; import 'streams/zip_test.dart' as zip_test; @@ -43,7 +39,23 @@ import 'transformers/any_test.dart' as any_test; import 'transformers/as_broadcast_stream.dart' as as_broadcast_stream; import 'transformers/async_expand_test.dart' as async_expand_test; import 'transformers/async_map_test.dart' as async_map_test; -import 'transformers/concat_map_test.dart' as concat_map_test; +import 'transformers/backpressure/buffer_count_test.dart' as buffer_count_test; +import 'transformers/backpressure/buffer_test.dart' as buffer_test; +import 'transformers/backpressure/buffer_test_test.dart' as buffer_test_test; +import 'transformers/backpressure/buffer_time_test.dart' as buffer_time_test; +import 'transformers/backpressure/debounce_test.dart' as debounce_test; +import 'transformers/backpressure/debounce_time_test.dart' + as debounce_time_test; +import 'transformers/backpressure/pairwise_test.dart' as pairwise_test; +import 'transformers/backpressure/sample_test.dart' as sample_test; +import 'transformers/backpressure/sample_time_test.dart' as sample_time_test; +import 'transformers/backpressure/throttle_test.dart' as throttle_test; +import 'transformers/backpressure/throttle_time_test.dart' + as throttle_time_test; +import 'transformers/backpressure/window_count_test.dart' as window_count_test; +import 'transformers/backpressure/window_test.dart' as window_test; +import 'transformers/backpressure/window_test_test.dart' as window_test_test; +import 'transformers/backpressure/window_time_test.dart' as window_time_test; import 'transformers/concat_with_test.dart' as concat_with_test; import 'transformers/contains_test.dart' as contains_test; import 'transformers/default_if_empty_test.dart' as default_if_empty_test; @@ -74,7 +86,6 @@ import 'transformers/last_where_test.dart' as last_where_test; import 'transformers/map_to_test.dart' as map_to_test; import 'transformers/materialize_test.dart' as materialize_test; import 'transformers/merge_with_test.dart' as merge_with_test; -import 'transformers/of_type_test.dart' as of_type_test; import 'transformers/on_error_return_test.dart' as on_error_resume_test; import 'transformers/on_error_return_test.dart' as on_error_return_test; import 'transformers/on_error_return_with_test.dart' @@ -103,24 +114,6 @@ import 'transformers/where_type_test.dart' as where_type_test; import 'transformers/with_latest_from_test.dart' as with_latest_from_test; import 'transformers/zip_with_test.dart' as zip_with_test; -import 'transformers/backpressure/buffer_count_test.dart' as buffer_count_test; -import 'transformers/backpressure/buffer_test.dart' as buffer_test; -import 'transformers/backpressure/buffer_test_test.dart' as buffer_test_test; -import 'transformers/backpressure/buffer_time_test.dart' as buffer_time_test; -import 'transformers/backpressure/debounce_test.dart' as debounce_test; -import 'transformers/backpressure/debounce_time_test.dart' - as debounce_time_test; -import 'transformers/backpressure/pairwise_test.dart' as pairwise_test; -import 'transformers/backpressure/sample_test.dart' as sample_test; -import 'transformers/backpressure/sample_time_test.dart' as sample_time_test; -import 'transformers/backpressure/throttle_test.dart' as throttle_test; -import 'transformers/backpressure/throttle_time_test.dart' - as throttle_time_test; -import 'transformers/backpressure/window_count_test.dart' as window_count_test; -import 'transformers/backpressure/window_test.dart' as window_test; -import 'transformers/backpressure/window_test_test.dart' as window_test_test; -import 'transformers/backpressure/window_time_test.dart' as window_time_test; - void main() { // Streams combine_latest_test.main(); @@ -153,7 +146,6 @@ void main() { as_broadcast_stream.main(); async_expand_test.main(); async_map_test.main(); - concat_map_test.main(); concat_with_test.main(); contains_test.main(); default_if_empty_test.main(); @@ -185,7 +177,6 @@ void main() { map_to_test.main(); materialize_test.main(); merge_with_test.main(); - of_type_test.main(); on_error_resume_test.main(); on_error_return_test.main(); on_error_return_with_test.main(); @@ -236,12 +227,6 @@ void main() { publish_subject_test.main(); replay_subject_test.main(); - // Futures - as_observable_future_test.main(); - stream_max_test.main(); - stream_min_test.main(); - wrapped_future_test.main(); - // Observables value_connectable_observable_test.main(); replay_connectable_observable_test.main(); diff --git a/test/streams/combine_latest_test.dart b/test/streams/combine_latest_test.dart index d5943ffbc..4dd698e24 100644 --- a/test/streams/combine_latest_test.dart +++ b/test/streams/combine_latest_test.dart @@ -330,17 +330,17 @@ void main() { /*test('rx.Observable.combineLatest.error.shouldThrowC', () { expect( - () => Observable.combineLatest3(new Observable.just(1), - new Observable.just(1), new Observable.just(1), null), + () => Observable.combineLatest3(Observable.just(1), + Observable.just(1), Observable.just(1), null), throwsArgumentError); }); test('rx.Observable.combineLatest.error.shouldThrowD', () { - expect(() => new CombineLatestStream(null, null), throwsArgumentError); + expect(() => CombineLatestStream(null, null), throwsArgumentError); }); test('rx.Observable.combineLatest.error.shouldThrowE', () { - expect(() => new CombineLatestStream(>[], null), + expect(() => CombineLatestStream(>[], null), throwsArgumentError); });*/ diff --git a/test/streams/zip_test.dart b/test/streams/zip_test.dart index 4f87c87ba..686620510 100644 --- a/test/streams/zip_test.dart +++ b/test/streams/zip_test.dart @@ -312,16 +312,16 @@ void main() { /*test('rx.Observable.zip.error.shouldThrowB', () { expect( () => Observable.zip2( - new Observable.just(1), null, (int a, _) => null), + Observable.just(1), null, (int a, _) => null), throwsArgumentError); }); test('rx.Observable.zip.error.shouldThrowC', () { - expect(() => new ZipStream(null, () {}), throwsArgumentError); + expect(() => ZipStream(null, () {}), throwsArgumentError); }); test('rx.Observable.zip.error.shouldThrowD', () { - expect(() => new ZipStream(>[], () {}), + expect(() => ZipStream(>[], () {}), throwsArgumentError); });*/ @@ -349,7 +349,6 @@ void main() { (index) => const [9, 10, 11, 12][index]); StreamSubscription> subscription; - // ignore: deprecated_member_use subscription = Observable.zip3(first, second, last, (num a, num b, num c) => [a, b, c]) .listen(expectAsync1((value) { diff --git a/test/transformers/backpressure/throttle_test.dart b/test/transformers/backpressure/throttle_test.dart index b866ffa91..6096c6537 100644 --- a/test/transformers/backpressure/throttle_test.dart +++ b/test/transformers/backpressure/throttle_test.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:rxdart/rxdart.dart'; import 'package:test/test.dart'; -Observable _observable() => +Stream _observable() => Observable.periodic(const Duration(milliseconds: 100), (i) => i + 1) .take(10); diff --git a/test/transformers/backpressure/throttle_time_test.dart b/test/transformers/backpressure/throttle_time_test.dart index 9aea30bf7..af21b02a3 100644 --- a/test/transformers/backpressure/throttle_time_test.dart +++ b/test/transformers/backpressure/throttle_time_test.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:rxdart/rxdart.dart'; import 'package:test/test.dart'; -Observable _observable() => +Stream _observable() => Observable.periodic(const Duration(milliseconds: 100), (i) => i + 1) .take(10); diff --git a/test/transformers/concat_map_test.dart b/test/transformers/concat_map_test.dart deleted file mode 100644 index 516ea37f9..000000000 --- a/test/transformers/concat_map_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/rxdart.dart'; -import 'package:test/test.dart'; - -void main() { - test('rx.Observable.concatMap', () async { - const expectedOutput = [1, 1, 2, 2, 3, 3]; - var count = 0; - - Observable(_getStream()).concatMap(_getOtherStream).listen( - expectAsync1((result) { - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length), onDone: expectAsync0(() { - expect(true, true); - })); - }); - - test('rx.Observable.concatMap.error.shouldThrow', () async { - final observableWithError = - Observable(ErrorStream(Exception())).concatMap(_getOtherStream); - - observableWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('rx.Observable.concatMap.pause.resume', () async { - StreamSubscription subscription; - final stream = Observable.just(0).concatMap((_) => Observable.just(1)); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - subscription.cancel(); - }, count: 1)); - - Timer(Duration(milliseconds: 10), () { - subscription.pause(); - subscription.resume(); - }); - }); - - test('rx.Observable.concatMap.cancel', () async { - StreamSubscription subscription; - final stream = Observable(_getStream()).concatMap(_getOtherStream); - - // Cancel the subscription before any events come through - subscription = stream.listen( - expectAsync1((value) { - expect(true, isFalse); - }, count: 0), - onError: expectAsync2((Exception e, StackTrace s) { - expect(true, isFalse); - }, count: 0), - onDone: expectAsync0(() { - expect(true, isFalse); - }, count: 0)); - - Timer(Duration(milliseconds: 5), () { - subscription.cancel(); - }); - }); -} - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3]); - -Stream _getOtherStream(int value) { - final controller = StreamController(); - - Timer( - // Reverses the order of 1, 2, 3 to 3, 2, 1 by delaying 1, and 2 longer - // than it delays 3 - Duration(milliseconds: value == 1 ? 20 : value == 2 ? 10 : 5), () { - controller.add(value); - }); - - // Delay by a longer amount, with the same reversing effect - Timer(Duration(milliseconds: value == 1 ? 30 : value == 2 ? 20 : 10), () { - controller.add(value); - controller.close(); - }); - - return controller.stream; -} diff --git a/test/transformers/dematerialize_test.dart b/test/transformers/dematerialize_test.dart index 30d37665b..99f1e6567 100644 --- a/test/transformers/dematerialize_test.dart +++ b/test/transformers/dematerialize_test.dart @@ -10,7 +10,7 @@ void main() { const expectedValue = 1; final observable = Observable.just(1).materialize(); - observable.dematerialize().listen(expectAsync1((value) { + observable.dematerialize().listen(expectAsync1((value) { expect(value, expectedValue); }), onDone: expectAsync0(() { // Should call onDone diff --git a/test/transformers/do_test.dart b/test/transformers/do_test.dart index 0f1a567ff..fe5f8e9fe 100644 --- a/test/transformers/do_test.dart +++ b/test/transformers/do_test.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'package:rxdart/rxdart.dart'; -import 'package:rxdart/src/transformers/do.dart'; -import 'package:rxdart/src/utils/notification.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/transformers/exhaust_map_test.dart b/test/transformers/exhaust_map_test.dart index 6bb570fed..defd3788b 100644 --- a/test/transformers/exhaust_map_test.dart +++ b/test/transformers/exhaust_map_test.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:rxdart/rxdart.dart'; -import 'package:rxdart/src/transformers/exhaust_map.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/transformers/handle_error_test.dart b/test/transformers/handle_error_test.dart index cfbe2eebc..901bcb68d 100644 --- a/test/transformers/handle_error_test.dart +++ b/test/transformers/handle_error_test.dart @@ -12,7 +12,6 @@ void main() { obs.listen(null, onError: expectAsync2((ArgumentError e, StackTrace s) { expect(e, isArgumentError); - expect(obs is Observable, isTrue); })); }); } diff --git a/test/transformers/max_test.dart b/test/transformers/max_test.dart new file mode 100644 index 000000000..347db3ed0 --- /dev/null +++ b/test/transformers/max_test.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:rxdart/rxdart.dart'; +import 'package:test/test.dart'; + +void main() { + test('rx.Observable.max', () async { + await expectLater(Observable(_getStream()).max(), completion(9)); + }); + + test('rx.Observable.max.with.comparator', () async { + await expectLater( + Observable.fromIterable(["one", "two", "three"]) + .max((String a, String b) => a.length - b.length), + completion("three")); + }); +} + +class ErrorComparator implements Comparable { + @override + int compareTo(ErrorComparator other) { + throw Exception(); + } +} + +Stream _getStream() => + Stream.fromIterable(const [2, 3, 3, 5, 2, 9, 1, 2, 0]); diff --git a/test/transformers/min_test.dart b/test/transformers/min_test.dart new file mode 100644 index 000000000..501a72c14 --- /dev/null +++ b/test/transformers/min_test.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:rxdart/rxdart.dart'; +import 'package:test/test.dart'; + +void main() { + test('rx.Observable.min', () async { + await expectLater(Observable(_getStream()).min(), completion(0)); + }); + + test('rx.Observable.min.with.comparator', () async { + await expectLater( + Observable.fromIterable(["one", "two", "three"]) + .min((String a, String b) => a.length - b.length), + completion("one")); + }); +} + +class ErrorComparator implements Comparable { + @override + int compareTo(ErrorComparator other) { + throw Exception(); + } +} + +Stream _getStream() => + Stream.fromIterable(const [2, 3, 3, 5, 2, 9, 1, 2, 0]); diff --git a/test/transformers/of_type_test.dart b/test/transformers/of_type_test.dart deleted file mode 100644 index e25ca8d04..000000000 --- a/test/transformers/of_type_test.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'package:rxdart/rxdart.dart'; -import 'package:rxdart/src/utils/type_token.dart'; -import 'package:test/test.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add('2')); - Timer( - const Duration(milliseconds: 300), () => controller.add(const {'3': 3})); - Timer(const Duration(milliseconds: 400), () { - controller.add(const {'4': '4'}); - }); - Timer(const Duration(milliseconds: 500), () { - controller.add(5.0); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('rx.Observable.ofType', () async { - Observable(_getStream()) - // ignore: deprecated_member_use_from_same_package - .ofType(TypeToken>()) - .listen(expectAsync1((result) { - expect(result, isMap); - }, count: 1)); - }); - - test('rx.Observable.ofType.polymorphism', () async { - // ignore: deprecated_member_use_from_same_package - Observable(_getStream()).ofType(kNum).listen(expectAsync1((result) { - expect(result is num, true); - }, count: 2)); - }); - - test('rx.Observable.ofType.asBroadcastStream', () async { - // ignore: deprecated_member_use_from_same_package - final stream = Observable(_getStream().asBroadcastStream()).ofType(kInt); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('rx.Observable.ofType.error.shouldThrow', () async { - final observableWithError = Observable(ErrorStream(Exception())) - // ignore: deprecated_member_use_from_same_package - .ofType(kNum); - - observableWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('rx.Observable.ofType.pause.resume', () async { - StreamSubscription subscription; - final stream = Observable.just(1) - // ignore: deprecated_member_use_from_same_package - .ofType(kInt); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); -} diff --git a/test/transformers/on_error_close_test.dart b/test/transformers/on_error_close_test.dart new file mode 100644 index 000000000..dad433101 --- /dev/null +++ b/test/transformers/on_error_close_test.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:rxdart/rxdart.dart'; +import 'package:test/test.dart'; + +void main() { + group('onErrorClose', () { + test('cancels a Stream when it emits an error', () { + final stream = ConcatStream([ + Stream.fromIterable([1]), + Stream.error(Exception()), + Stream.fromIterable([2]), + ]); + + expect( + stream.onErrorClose(), + emitsInOrder([1, emitsDone]), + ); + }); + + test('cancels a Stream when the predicate returns true', () { + final stream = ConcatStream([ + Stream.fromIterable([1]), + Stream.error(Exception()), + Stream.fromIterable([2]), + Stream.error(StateError('Oh no')), + Stream.fromIterable([3]), + ]); + + expect( + stream.onErrorClose((dynamic e) => e is StateError), + emitsInOrder([1, emitsError(isException), 2, emitsDone]), + ); + }); + + test('does not affect the Stream if no errors are emitted', () { + final stream = ConcatStream([ + Stream.fromIterable([1]), + Stream.fromIterable([2]), + Stream.fromIterable([3]), + ]); + + expect( + stream.onErrorClose(), + emitsInOrder([1, 2, 3, emitsDone]), + ); + }); + }); +} diff --git a/test/transformers/switch_map_test.dart b/test/transformers/switch_map_test.dart index 1e20824b8..08af27299 100644 --- a/test/transformers/switch_map_test.dart +++ b/test/transformers/switch_map_test.dart @@ -122,8 +122,7 @@ void main() { test('rx.Observable.switchMap stream close after switch', () async { final controller = StreamController(); - - Observable(controller.stream) + final list = controller.stream .switchMap((it) => Stream.fromIterable([it, it])) .toList(); @@ -132,5 +131,6 @@ void main() { controller.add(2); await controller.close(); + expect(await list, [1, 1, 2, 2]); }); }