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 ValueStream
s, making it easier to build reactive UIs in Flutter.
This package provides three main widgets:
ValueStreamBuilder
: A widget that rebuilds UI based onValueStream
updatesValueStreamListener
: A widget that performs side effects whenValueStream
values changeValueStreamConsumer
: A widget combining both builder and listener capabilities forValueStream
s
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
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 ValueStream
s.
- Always has access to the current value (no
AsyncSnapshot
needed) - Optional
buildWhen
condition for controlling rebuilds - Proper error handling for streams without values or with errors
- Efficient rebuilding only when necessary
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
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.
- 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
child
for stable widgets that remain unchanged across stream updates
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
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.
- Combined builder and listener functionality
- Optional
buildWhen
condition for controlling rebuilds - Access to previous and current values in listener
- Efficient handling of both UI updates and side effects
- Optional
child
for stable widgets that remain unchanged across stream updates
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
)
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
BehaviorSubject
or anotherValueStream
with 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