Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rxdart_flutter): add ValueStream widgets #782

Merged
merged 11 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 105 additions & 29 deletions packages/rxdart_flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,133 @@
[![License](https://img.shields.io/github/license/ReactiveX/rxdart)](https://www.apache.org/licenses/LICENSE-2.0)
[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FReactiveX%2Frxdart&count_bg=%23D71092&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)

`rxdart_flutter` is a Flutter package that provides a set of widgets for working with `rxdart`.

These widgets are specifically designed to work with `ValueStream`s, making it easier to build reactive UIs in Flutter.

## Overview

This package provides three main widgets:
- `ValueStreamBuilder`: A widget that rebuilds UI based on `ValueStream` updates
- `ValueStreamListener`: A widget that performs side effects when `ValueStream` values change
- `ValueStreamConsumer`: A widget combining both builder and listener capabilities for `ValueStream`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

`ValueStreamBuilder` is a widget similar to `StreamBuilder`, but works with `ValueStream` and simplifies the process of rebuilding widgets in response to stream updates. It provides a more streamlined API and performance improvements for working with streams that always have a value and do not emit errors.
`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.

### Features
- Works with `ValueStream` instead of `Stream`.
- Automatically rebuilds the widget when the stream emits new data.
- Supports optional `buildWhen` callback for more granular control over rebuild behavior.

### Usage
- 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

#### Basic Example
### Example

```dart
final valueStream = BehaviorSubject<int>.seeded(0);
final counterStream = BehaviorSubject<int>.seeded(0); // Initial value required

ValueStreamBuilder<int>(
stream: valueStream,
builder: (context, data) {
// return widget here based on data
return Text('Current value: $data');
stream: counterStream,
buildWhen: (previous, current) => current != previous, // Optional rebuild condition
builder: (context, value) {
return Text(
'Counter: $value',
style: Theme.of(context).textTheme.headlineMedium,
);
},
);
)
```

#### Example with `buildWhen`
## 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.

You can provide an optional `buildWhen` callback to control when the widget should be rebuilt based on changes to the data.
### 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

### Example

```dart
ValueStreamBuilder<int>(
stream: valueStream,
buildWhen: (previous, current) {
// Only rebuild if the current value is different from the previous value
return previous != current;
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');
}
},
builder: (context, data) {
return Text('Current value: $data');
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 `buildWhen` condition for controlling rebuilds
- Access to previous and current values in listener
- Efficient handling of both UI updates and side effects

### Example

```dart
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) {
return Column(
children: [
Text('Total items: ${cart.itemCount}'),
Text('Total price: \$${cart.totalPrice}'),
],
);
},
);
)
```

### Parameters
## Error Handling

- **`stream`**: The `ValueStream` to listen to. The stream must have a value at all times and must not emit errors.
- **`builder`**: A callback that returns the widget to display based on the stream data.
- **`buildWhen`**: An optional callback that determines whether to rebuild the widget based on the previous and current data. Defaults to `true` (always rebuilds).
All widgets in this package handle two types of errors:

### Error Handling
1. `ValueStreamHasNoValueError`: Thrown when the stream doesn't have an initial value
2. `UnhandledStreamError`: Thrown when the stream emits an error

`ValueStreamBuilder` requires the stream to always have a value and never emit errors. If an error occurs in the stream, it will be displayed using the `ErrorWidget`.
To avoid these errors:
- Always use `BehaviorSubject` or another `ValueStream` with an initial value
- Handle stream errors before they reach these widgets
- Consider using `stream.handleError()` to transform errors if needed

If the stream has no value when the builder is first called, a `ValueStreamHasNoValueError` will be thrown. You can handle this by ensuring that the stream is seeded with an initial value or by checking if the stream has a value before using `ValueStreamBuilder`.
Example of proper stream initialization:
```dart
// 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
```
54 changes: 54 additions & 0 deletions packages/rxdart_flutter/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

# Scaffolded code by `flutter create .`
android/
ios/
linux/
macos/
web/
windows/
test/widget_test.dart
45 changes: 45 additions & 0 deletions packages/rxdart_flutter/example/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "17025dd88227cd9532c33fa78f5250d548d87e9a"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: android
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: ios
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: linux
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: macos
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: web
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: windows
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
9 changes: 9 additions & 0 deletions packages/rxdart_flutter/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# rxdart_flutter_example

## Getting Started

Generate platform-specific code

```bash
flutter create .
```
1 change: 1 addition & 0 deletions packages/rxdart_flutter/example/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:flutter_lints/flutter.yaml
Loading