Skip to content

Commit 94d6fe6

Browse files
committed
Add pipeToCommand() extension for command chaining (v9.5.0)
New ValueListenable extension that triggers a target command when the source value changes. Works on any ValueListenable including commands, isRunning, results, or plain ValueNotifier. Features: - Optional transform function to convert source value to target param - Smart type handling: direct pass if types match, no-param call if not - Returns ListenableSubscription for manual cancellation
1 parent 758eef5 commit 94d6fe6

File tree

5 files changed

+609
-1
lines changed

5 files changed

+609
-1
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
[9.5.0] - 2025-11-29
2+
3+
### New Features
4+
5+
- **Command Piping**: New `pipeToCommand()` extension on `ValueListenable<T>` to chain commands together
6+
- When the source ValueListenable changes, automatically triggers the target command
7+
- Works on any ValueListenable: commands, `isRunning`, `results`, or plain `ValueNotifier`
8+
- Optional `transform` parameter to convert source value to target parameter type
9+
- Smart type handling: passes value directly if types match, calls `run()` without param if they don't
10+
- Returns `ListenableSubscription` for manual cancellation
11+
12+
**Usage Examples**:
13+
```dart
14+
// Trigger refresh after save completes
15+
saveCommand.pipeToCommand(refreshCommand);
16+
17+
// Transform result before passing to target
18+
userIdCommand.pipeToCommand(fetchUserCommand, transform: (id) => UserRequest(id));
19+
20+
// Pipe from isRunning to track execution state
21+
longCommand.isRunning.pipeToCommand(spinnerCommand);
22+
```
23+
124
[9.4.2] - 2025-11-25
225

326
### Maintenance

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,28 @@ Declarative error routing with filters:
166166
- **[Command Restrictions](https://flutter-it.dev/documentation/command_it/restrictions)** — Disable commands conditionally
167167
- **[CommandBuilder](https://flutter-it.dev/documentation/command_it/command_builders)** — Widget for simpler UI code
168168
- **[Undoable Commands](https://flutter-it.dev/documentation/command_it/undoable_commands)** — Built-in undo/redo support
169+
- **[Command Piping](#piping-commands)** — Chain commands together automatically
169170
- **[Testing](https://flutter-it.dev/documentation/command_it/testing)** — Patterns for testing commands
170171

172+
### Piping Commands
173+
174+
Chain commands together with the `pipeToCommand()` extension. When the source completes successfully, it automatically triggers the target command:
175+
176+
```dart
177+
// Trigger refresh after save completes
178+
saveCommand.pipeToCommand(refreshCommand);
179+
180+
// Transform result before passing to target
181+
userIdCommand.pipeToCommand(fetchUserCommand, transform: (id) => UserRequest(id));
182+
183+
// Pipe from any ValueListenable - track execution state changes
184+
longRunningCommand.isRunning.pipeToCommand(spinnerStateCommand);
185+
```
186+
187+
The `pipeToCommand()` extension works on any `ValueListenable`, including commands, `isRunning`, `results`, or plain `ValueNotifier`. Returns a `ListenableSubscription` for manual cancellation if needed.
188+
189+
> ⚠️ **Warning:** Circular pipes (A→B→A) cause infinite loops. Ensure your pipe graph is acyclic.
190+
171191
## Ecosystem Integration
172192

173193
**Built on listen_it** — Commands are `ValueListenable` objects, so they work with all listen_it operators (map, debounce, where, etc.).

lib/command_it.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,3 +2394,50 @@ abstract class Command<TParam, TResult> extends CustomValueNotifier<TResult> {
23942394
return command;
23952395
}
23962396
}
2397+
2398+
/// Extension to pipe [ValueListenable] changes to a [Command].
2399+
///
2400+
/// This allows chaining commands - when the source ValueListenable changes,
2401+
/// it automatically triggers the target command.
2402+
///
2403+
/// **Warning**: Circular pipes (A→B→A) will cause infinite loops.
2404+
/// Ensure your pipe graph is acyclic.
2405+
///
2406+
/// Example:
2407+
/// ```dart
2408+
/// // Trigger refresh after save completes
2409+
/// saveCommand.pipeToCommand(refreshCommand);
2410+
///
2411+
/// // With transform function
2412+
/// userIdCommand.pipeToCommand(fetchUserCommand, transform: (id) => UserRequest(id));
2413+
///
2414+
/// // Pipe from isRunning to track execution state
2415+
/// longCommand.isRunning.pipeToCommand(spinnerCommand);
2416+
/// ```
2417+
extension ValueListenablePipe<T> on ValueListenable<T> {
2418+
/// Triggers [target] command whenever this ValueListenable's value changes.
2419+
///
2420+
/// - If [transform] is provided, transforms the value before passing to target
2421+
/// - If [transform] is null and value is assignable to TTargetParam, passes directly
2422+
/// - If [transform] is null and types don't match, calls target.run() without param
2423+
/// (useful for triggering no-param commands)
2424+
///
2425+
/// Returns the [ListenableSubscription] for manual cancellation if needed.
2426+
/// Normally, the subscription is automatically cleaned up when the source
2427+
/// ValueListenable is disposed.
2428+
ListenableSubscription pipeToCommand<TTargetParam, TTargetResult>(
2429+
Command<TTargetParam, TTargetResult> target, {
2430+
TTargetParam Function(T value)? transform,
2431+
}) {
2432+
return listen((value, _) {
2433+
if (transform != null) {
2434+
target.run(transform(value));
2435+
} else if (value is TTargetParam) {
2436+
target.run(value);
2437+
} else {
2438+
// Types don't match, call without param (for void/no-param targets)
2439+
target.run();
2440+
}
2441+
});
2442+
}
2443+
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: command_it
22
description: command_it is a way to manage your state based on `ValueListenable` and the `Command` design pattern. It is a rebranding of flutter_command.
3-
version: 9.4.2
3+
version: 9.5.0
44
homepage: https://github.com/flutter-it/command_it
55

66
screenshots:

0 commit comments

Comments
 (0)