https://github.com/flutterando/result_command
A command pattern implementation for Dart and Flutter using result_dart package.
https://github.com/flutterando/result_command
dart flutter
Last synced: about 2 months ago
JSON representation
A command pattern implementation for Dart and Flutter using result_dart package.
- Host: GitHub
- URL: https://github.com/flutterando/result_command
- Owner: Flutterando
- License: other
- Created: 2024-12-10T23:39:10.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-01-31T21:26:41.000Z (5 months ago)
- Last Synced: 2025-03-31T10:11:43.066Z (3 months ago)
- Topics: dart, flutter
- Language: Dart
- Homepage: https://pub.dev/packages/result_command
- Size: 341 KB
- Stars: 5
- Watchers: 1
- Forks: 4
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Result Command
**Result Command** is a lightweight package that brings the **Command Pattern** to Flutter, allowing you to encapsulate actions, track their execution state, and manage results with clarity. Perfect for simplifying complex workflows, ensuring robust error handling, and keeping your UI reactive.
---
## Getting Started
1. Add the package to your `pubspec.yaml`:
```yaml
dependencies:
result_command: x.x.x
result_dart: 2.x.x
```2. Wrap your actions in commands:
- Use pre-defined `Command` types to encapsulate your logic.
- Attach listeners to update your UI dynamically.3. Execute commands from your UI and enjoy the benefits of encapsulated logic and state tracking.
---
## Why Use Result Command?
1. **Encapsulation**: Wrap your business logic into reusable commands.
2. **State Tracking**: Automatically manage states like `Idle`, `Running`, `Success`, `Failure`, and `Cancelled`.
3. **Error Handling**: Centralize how you handle successes and failures using the intuitive `Result` API.
4. **State History**: Track state transitions with optional metadata.
5. **Timeout Support**: Specify execution time limits for commands.
6. **Cancellation Support**: Cancel long-running tasks when needed.
7. **UI Integration**: React to command state changes directly in your Flutter widgets.---
## How It Works
At its core, **Result Command** lets you define reusable actions (commands) that manage their lifecycle. Each command:
- **Executes an action** (e.g., API call, user input validation).
- **Tracks its state** (`Idle`, `Running`, etc.).
- **Notifies listeners** when the state changes.
- **Maintains a history** of states and transitions.
- **Returns a result** (`Success` or `Failure`) using the `Result` API.---
## Command State (`CommandState`)
Each `Command` exposes its current state through a `CommandState`. The state represents one of the following:
- **`IdleCommand`**: The command is ready to execute.
- **`RunningCommand`**: The command is currently executing an action.
- **`SuccessCommand`**: The action completed successfully.
- **`FailureCommand`**: The action failed with an error.
- **`CancelledCommand`**: The action was explicitly stopped.### Accessing the State
You can access the current state using the `value` property of the command:
```dart
final command = Command0(() async {
return Success('Hello, World!');
});// The current state of the command.
print(command.value); // Outputs: SuccessCommand
```### Reacting to State Changes
The state updates automatically as the command executes:
- Use `addListener` for manual handling.
- Use `ValueListenableBuilder` to bind the state to your UI.## State History (`CommandHistory`)
Each command tracks a configurable history of its states, useful for debugging, auditing, and behavioral analysis.
### Configuring the History
Set the maximum length of the history when creating a command:
```dart
final command = Command0(
() async => const Success('Done'),
maxHistoryLength: 5,
);
```### Accessing the History
Access the history with `stateHistory`:
```dart
final history = command.stateHistory;
history.forEach(print);
```---
## Getters for State Checks
To simplify state management and improve code readability, the following getters are available:
- **`isIdle`**: Checks if the command is in the idle state.
```dart
bool get isIdle => value is IdleCommand;
```- **`isRunning`**: Checks if the command is currently running.
```dart
bool get isRunning => value is RunningCommand;
```- **`isCancelled`**: Checks if the command has been cancelled.
```dart
bool get isCancelled => value is CancelledCommand;
```- **`isSuccess`**: Checks if the command execution was successful.
```dart
bool get isSuccess => value is SuccessCommand;
```- **`isFailure`**: Checks if the command execution failed.
```dart
bool get isFailure => value is FailureCommand;
```These getters allow you to write cleaner and more intuitive code when interacting with commands in your views or controllers.
---
## Observer
Sets the default observer listener for all commands.
This listener is called whenever the state of any command changes.
This can be useful for logging, debugging, or global state management.```dart
main(){Command.setObserverListener((state) {
print(state);
});runApp(MainApp());
}
```## Examples
### Example 1: Simple Command with No Arguments
Encapsulate a simple action into a reusable `Command`:
```dart
final fetchGreetingCommand = Command0(
() async {
await Future.delayed(Duration(seconds: 2));
return Success('Hello, World!');
},
);fetchGreetingCommand.addListener(() {
final snapshot = fetchGreetingCommand.value;
if (snapshot is Success) {
final result = snapshot.value;
print('Success: $result');
} else if (snapshot is FailureCommand) {
final error = snapshot.error;
print('Failure: $error');
}
});// Execute the command
fetchGreetingCommand.execute();
```---
### Example 2: Simple Command with Timeout
Commands now support a timeout for execution:
```dart
final fetchGreetingCommand = Command0(
() async {
await Future.delayed(Duration(seconds: 5)); // Simulating a delay.
return Success('Hello, World!');
},
);fetchGreetingCommand //
.execute(timeout: Duration(seconds: 2))
.catchError((error) {
print('Error: $error'); // "Error: Command timed out"
},
);
```---
### Example 3: Command with Arguments
Pass input to your command's action:
```dart
final calculateSquareCommand = Command1(
(number) async {
if (number < 0) {
return Failure(Exception('Negative numbers are not allowed.'));
}
return Success(number * number);
},
);calculateSquareCommand.addListener(() {
final snapshot = calculateSquareCommand.value;
if (snapshot is SuccessCommand) {
final result = snapshot.value;
print('Square: $result');
} else if (snapshot is FailureCommand) {
final error = snapshot.error;
print('Error: $error');
}
});// Execute the command with input
calculateSquareCommand.execute(4);
```---
### Example 4: Binding State to the UI
Use `ValueListenableBuilder` to update the UI automatically:
```dart
final loginCommand = Command2(
(username, password) async {
if (username == 'admin' && password == 'password') {
return Success(true);
}
return Failure(Exception('Invalid credentials.'));
},
);Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder>(
valueListenable: loginCommand,
builder: (context, state, child) {
if (state is RunningCommand) {
return CircularProgressIndicator();
} else if (state is SuccessCommand) {
return Text('Login Successful!');
} else if (state is FailureCommand) {
return Text('Login Failed: ${(state as FailureCommand).error}');
}
return ElevatedButton(
onPressed: () => loginCommand.execute('admin', 'password'),
child: Text('Login'),
);
},
),
],
);
}
```---
### Example 5: Cancellation
Cancel long-running commands gracefully:
```dartIsolate? _uploadIsolate;
void _uploadAction() {
// uploading code...
}final uploadCommand = Command0(
() async {
_uploadIsolate = await Isolate.spawn(_uploadAction, []);
},
onCancel: () {
_uploadIsolate.kill();
},
);// Start the upload
uploadCommand.execute();// Cancel after 3 seconds
Future.delayed(Duration(seconds: 3), () {
uploadCommand.cancel();
});
```---
### Example 5: using the map function
Cancel long-running commands gracefully:
```dartfinal calculateSquareCommand = Command1(
(number) async {
if (number < 0) {
return Failure(Exception('Negative numbers are not allowed.'));
}
return Success(number * number);
},
);calculateSquareCommand.addListener(() {
final message = calculateSquareCommand.value.when(
data: (value) => 'Square: $value',
failure: (exception) => 'Error: ${exception?.message}',
running: () => 'Calculating...',
orElse: () => 'default value',
);// Display the message in a snackbar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
});
// Execute the command with input
calculateSquareCommand.execute(4);
``````dart
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder>(
valueListenable: loginCommand,
builder: (context, state, child) {
return state.when(
data: (_) => const Text('Login Successful!'),
running: () => const CircularProgressIndicator(),
failure: (error) => Text('Login Failed: $error'),
orElse: () => ElevatedButton(
onPressed: () => loginCommand.execute('admin', 'password'),
child: Text('Login'),
),
);
},
),
],
);
}
```#### Minimal Usage: Handling Only Success State and orElse
```dartfinal calculateSquareCommand = Command1(
(number) async {
if (number < 0) {
return Failure(Exception('Negative numbers are not allowed.'));
}
return Success(number * number);
},
);calculateSquareCommand.addListener(() {
final message = calculateSquareCommand.value.when(
data: (value) => 'Square: $value',
orElse: () => 'default value',
);// Display the message in a snackbar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
});
// Execute the command with input
calculateSquareCommand.execute(4);
```**Notes**
- The function ensures type safety by requiring `data to handle the `success` state explicitly.
- The `orElse` callback is useful for dealing with unexpected states or adding default behavior.----
## Benefits for Your Team
- **Simplified Collaboration**: Encapsulation makes it easier for teams to work independently on UI and business logic.
- **Reusability**: Commands can be reused across different widgets or flows.
- **Maintainability**: Cleaner separation of concerns reduces technical debt.---
## Contribute
We’d love your help in improving **Result Command**! Feel free to report issues, suggest features, or submit pull requests.
---