RxDart Flutter
rxdart_flutter is a Flutter package that provides a set of widgets for working with rxdart.
This package is officially maintained in the ReactiveX/rxdart repository.
These widgets are specifically designed to work with ValueStreams, making it easier to build reactive UIs in Flutter.
Overview
This package provides three main widgets:
ValueStreamBuilder: A widget that rebuilds UI based onValueStreamupdatesValueStreamListener: A widget that performs side effects whenValueStreamvalues changeValueStreamConsumer: A widget combining both builder and listener capabilities forValueStreams
All widgets require a ValueStream that always has a value and never emits errors. If these conditions are not met, appropriate error widgets will be displayed.
ValueStreamBuilder
ValueStreamBuilder is a widget that builds itself based on the latest value emitted by a ValueStream. It's similar to Flutter's StreamBuilder but specifically optimized for ValueStreams.
Features
- Always has access to the current value (no
AsyncSnapshotneeded) - Optional
buildWhencondition for controlling rebuilds - Proper error handling for streams without values or with errors
- Efficient rebuilding only when necessary
Example
final counterStream = BehaviorSubject<int>.seeded(0); // Initial value required
ValueStreamBuilder<int>(
stream: counterStream,
buildWhen: (previous, current) => current != previous, // Optional rebuild condition
builder: (context, value, child) {
return Column(
children: [
Text(
'Counter: $value',
style: Theme.of(context).textTheme.headlineMedium,
),
if (child != null) child, // Use the stable child widget if provided
],
);
},
child: const Text('This widget remains stable'), // Optional stable child widget
)
ValueStreamListener
ValueStreamListener is a widget that executes callbacks in response to stream value changes. It's perfect for handling side effects like showing snackbars, dialogs, or navigation.
Features
- Access to both previous and current values in the listener
- No rebuilds on value changes (unlike ValueStreamBuilder)
- Child widget is preserved across stream updates
- Guaranteed to only call listener once per value change
- Optional
childfor stable widgets that remain unchanged across stream updates
Example
final authStream = BehaviorSubject<AuthState>.seeded(AuthState.initial);
ValueStreamListener<AuthState>(
stream: authStream,
listener: (context, previous, current) {
if (previous.isLoggedOut && current.isLoggedIn) {
Navigator.of(context).pushReplacementNamed('/home');
} else if (previous.isLoggedIn && current.isLoggedOut) {
Navigator.of(context).pushReplacementNamed('/login');
}
},
child: MyApp(), // Child widget remains stable
)
ValueStreamConsumer
ValueStreamConsumer combines the functionality of both ValueStreamBuilder and ValueStreamListener. Use it when you need to both rebuild the UI and perform side effects in response to stream changes.
Features
- Combined builder and listener functionality
- Optional
buildWhencondition for controlling rebuilds - Access to previous and current values in listener
- Efficient handling of both UI updates and side effects
- Optional
childfor stable widgets that remain unchanged across stream updates
Example
final cartStream = BehaviorSubject<Cart>.seeded(Cart.empty());
ValueStreamConsumer<Cart>(
stream: cartStream,
buildWhen: (previous, current) => current.itemCount != previous.itemCount,
listener: (context, previous, current) {
if (current.itemCount > previous.itemCount) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Item added to cart')),
);
}
},
builder: (context, cart, child) {
return Column(
children: [
Text('Total items: ${cart.itemCount}'),
Text('Total price: \$${cart.totalPrice}'),
if (child != null) child, // Use the stable child widget if provided
],
);
},
child: const Text('This widget remains stable'), // Optional stable child widget
)
Error Handling
All widgets in this package handle two types of errors:
ValueStreamHasNoValueError: Thrown when the stream doesn't have an initial valueUnhandledStreamError: Thrown when the stream emits an error
To avoid these errors:
- Always use
BehaviorSubjector anotherValueStreamwith an initial value - Handle stream errors before they reach these widgets
- Consider using
stream.handleError()to transform errors if needed
Example of proper stream initialization:
// Good - stream has initial value
final goodStream = BehaviorSubject<int>.seeded(0);
// Bad - stream has no initial value
final badStream = BehaviorSubject<int>(); // Will throw ValueStreamHasNoValueError
// Bad - stream with error
final errorStream = BehaviorSubject<int>.seeded(0)..addError(Exception()); // Will throw UnhandledStreamError