RxDart Flutter
rxdart_flutter is a Flutter package that provides a set of widgets for working with rxdart.
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
