Overview of RxDart
masterStream API to provide advanced operators and functional programming capabilities for handling asynchronous data streams.repository·master·Indexed 25 days ago
https://github.com/reactivex/rxdartA ReactiveX implementation for Dart that extends the native Stream API with powerful operators, Subjects (BehaviorSubject and ReplaySubject), and specialized Stream classes. Includes the rxdart_flutter package providing ValueStreamBuilder, ValueStreamListener, and ValueStreamConsumer for integrating reactive streams into Flutter UIs.
Stream API to provide advanced operators and functional programming capabilities for handling asynchronous data streams.RxDart extends the capabilities of Dart Streams and StreamControllers. Rather than replacing the Dart Stream API, RxDart adds functionality from the ReactiveX specification on top of it by providing:
Stream classesStream class)SubjectsTo run the command line examples (such as the Fibonacci example), follow these steps:
pub get.dart, passing any required arguments (e.g., a number for the Fibonacci sequence).pub get
dart examples/fibonacci/lib/example.dart 10rxdart_flutter_example project and generate the necessary platform-specific code, run the flutter create . command within the package directory.flutter create .To run the Flutter Github Search example, ensure that Dart 2 is enabled in your environment. Use the following command to execute the application:
flutter run --preview-dart-2To run the web-based examples provided in the repository, follow these steps in your terminal:
examples/web directory.dart pub get.webdev is installed globally via dart pub global activate webdev.webdev serve.http://localhost:8080/.cd examples/web
dart pub get
dart pub global activate webdev
webdev serveWhen using async* generators, avoid awaiting a never-completing Future immediately after a yield if downstream code relies on the stream's cancellation settling (e.g., using Stream.first, Stream.single, or await for with a break).
If a generator enters an await that never completes, it cannot be cancelled. The cancellationFuture will never complete because the generator body never returns or throws, causing the calling code to hang indefinitely. This is a property of the Dart async* runtime, not a bug in RxDart.
Best Practices:
async* generators can eventually return or throw an error so cancellation can complete.StreamController-based implementation with explicit onCancel cleanup instead of parking an async* generator on an infinite await. This makes cancellation semantics explicit and ensures the cancellation future settles predictably.Observable class in favor of Dart's extension methods. To automate the refactoring required for this change, use the rxdart_codemod package.To run the Flutter examples (e.g., github_search), ensure you have Flutter installed and an emulator or device connected. Follow these steps:
examples/flutter/github_search directory.flutter doctor.flutter packages get.flutter run.cd examples/flutter/github_search
flutter doctor
flutter packages get
flutter runThe rxdart_flutter widgets will display error widgets if the provided stream violates certain conditions. To prevent errors, ensure your stream meets these requirements:
BehaviorSubject.seeded(value) instead of a plain BehaviorSubject(). If a stream has no initial value, it will throw ValueStreamHasNoValueError.stream.handleError()). If a stream emits an error, it will throw UnhandledStreamError.Correct Initialization Examples:
// Good - stream has initial value
final goodStream = BehaviorSubject<int>.seeded(0);
// Bad - stream has no initial value (throws ValueStreamHasNoValueError)
final badStream = BehaviorSubject<int>();
// Bad - stream with error (throws UnhandledStreamError)
final errorStream = BehaviorSubject<int>.seeded(0)..addError(Exception());A known issue exists when composing async* generators that contain infinite await calls (e.g., await Completer<void>().future) using operators like Rx.combineLatest2 and switchMap.
When you call await stream.first, the stream attempts to cancel its subscriptions once the first value is received. However, if one of the source streams is an async* generator suspended at an await (rather than a yield), Dart's runtime does not resume the generator upon cancellation. This causes the generator's cancellationFuture to remain incomplete. Because Rx.combineLatest waits for all source subscriptions to cancel via Future.wait, the entire cancellation chain hangs, and await stream.first will never complete (or will time out).
To avoid this:
async* generators do not stay suspended at an infinite await if they are part of a combined stream that needs to be canceled.async*, ensure they can be properly closed or that the composition doesn't rely on Stream.first which triggers a full subscription cancellation.import 'dart:async';
import 'dart:io';
import 'package:rxdart/rxdart.dart';
Future<void> main() async {
final stream = Rx.combineLatest2(
_emitOnceAndNeverClose('left'),
Stream.value('right').switchMap(_emitOnceAndNeverClose),
(left, right) => '$left|$right',
);
// This will hang or timeout if the generators are stuck at an await
final value = await stream.first.timeout(const Duration(milliseconds: 200));
stdout.writeln(value);
}
Stream<T> _emitOnceAndNeverClose<T>(T value) async* {
yield value;
await Completer<void>().future; // This infinite await causes the hang on cancellation
}RxDart provides extension methods that can be called on any Dart Stream to transform it into a new Stream with different capabilities, such as throttling, buffering, or delaying events.
Example of using throttleTime to limit event frequency:
Stream.fromIterable([1, 2, 3])
.throttleTime(Duration(seconds: 1))
.listen(print); // prints 1