commandy 0.1.1
commandy: ^0.1.1 copied to clipboard
A modern Flutter package for managing asynchronous commands and state effectively.
Commandy #
Commandy is a Flutter package designed to streamline the execution and management of asynchronous commands, while providing a clear and consistent way to handle success, failure, and state changes. It helps you abstract away the complexity of asynchronous actions, making your code more modular, testable, and maintainable.
Key Features 🚀 #
-
Unified Result Handling:
Uses aResult
model withSuccess<T>
andFailureResult<T>
variants to represent operation outcomes clearly. This encourages a functional approach to error handling and makes your code easier to reason about. -
Asynchronous Commands:
Command<T, Params>
encapsulates an asynchronous action, notifies execution state changes (isExecuting
), and delivers the outcome as aResult<T>
through aValueNotifier
. This pattern makes it simple to integrate async logic into your ViewModels or UseCases. -
Stream-Based Commands:
StreamCommand<T, Params>
handles actions that return aStream<Result<T>>
, automatically managing subscriptions and providing updates viaValueNotifier
. This is ideal for continuously updating data sources, such as real-time queries or sensor feeds. -
Command Listener for UI:
TheCommandListener
widget easily integrates into Flutter UI code. It listens to multiple commands and triggers callbacks when results change, allowing the UI layer to react immediately to new data or errors. -
NoParams for Simplicity:
Provides a convenient way to manage commands that do not require parameters, avoiding null values or unnecessary complexity. -
State Management Ready:
While Commandy focuses on commands, it can also be used as a lightweight state management solution. By encapsulating your business logic and state changes in commands, you get a clean and testable approach to managing application state.
Installation 💻 #
Add Commandy to your pubspec.yaml
:
dependencies:
commandy: ^0.1.0
Then, fetch packages:
flutter pub get
Getting Started #
-
Import the library:
import 'package:commandy/commandy.dart';
-
Create a Command that executes your asynchronous logic, for example, fetching data from a repository.
-
Bind it to your UI using a
CommandListener
widget or by callingexecute
and listening to result changes in your ViewModel.
By structuring your app's async operations as Commands returning Results, you can maintain a clean separation of concerns and ensure that logic and UI remain loosely coupled.
Core Concepts #
Result and its Variants #
Result<T>
is a sealed class that represents the outcome of an operation. It has two main variants:
Success<T>
: Indicates that the operation completed successfully. Holds the resultingdata
.FailureResult<T>
: Indicates that the operation failed. Holds aFailure
object with details of the error, including a message, optional exception, and stack trace.
NoParams Class 🛠️ #
NoParams
is a lightweight class that represents the absence of parameters for a command. It helps maintain a consistent Command<T, Params>
interface even when no parameters are required.
Example Usage:
final incrementCommand = Command<int, NoParams>((_) async {
// Increment logic
await Future.delayed(const Duration(seconds: 1));
return Success(42);
});
// Execute the command
await incrementCommand.execute(const NoParams());
This avoids passing null
and ensures clarity and consistency in your codebase.
Command #
Encapsulates an asynchronous action and updates its state via ValueNotifier
s:
isExecuting
: AValueNotifier<bool>
indicating if the command is still running.result
: AValueNotifier<Result<T>?>
holding the last result.
Use Command
for one-shot operations like login requests or data fetching.
StreamCommand #
Works with streams and manages ongoing updates. Call start(params)
to begin listening and stop()
to cancel the subscription.
CommandListener #
Simplifies UI integration by reacting to command result changes. Instead of manually listening to ValueNotifier
s, use CommandListener
to handle updates efficiently.
Puedes agregar la documentación de la nueva clase ViewModelSelector
en una nueva sección dedicada a "Advanced Features" o "UI Optimization". Esta clase encaja perfectamente en la idea de optimizar el comportamiento de widgets y el estado de la UI, así que deberíamos destacarla como una herramienta avanzada.
Advanced Features ⚡
ViewModelSelector 🧩 #
The ViewModelSelector
is a powerful utility for selectively rebuilding Flutter widgets based on specific properties of a ChangeNotifier
-based ViewModel. It optimizes UI updates by ensuring that only the widgets depending on the selected value are rebuilt, avoiding unnecessary widget rebuilds when other properties in the ViewModel change.
Key Features:
-
Selective Rebuilding:
Extracts a specific property from the ViewModel using aselector
function and only triggers a rebuild when that property changes. -
Integration with ChangeNotifier:
Works seamlessly with your existing ViewModels, which extendChangeNotifier
. -
Efficient and Modular:
Allows finer control of UI updates without requiring external libraries or tools.
Example Usage:
Here’s how you can integrate the ViewModelSelector
into your code:
1. Create a ViewModel:
class MyViewModel extends ChangeNotifier {
String _name = 'John Doe';
int _counter = 0;
String get name => _name;
int get counter => _counter;
void updateName(String newName) {
_name = newName;
notifyListeners();
}
void incrementCounter() {
_counter++;
notifyListeners();
}
}
2. Use ViewModelSelector in Your Widget:
final myViewModel = MyViewModel();
ViewModelSelector<MyViewModel, String>(
viewModel: myViewModel,
selector: (vm) => vm.name, // Watch only the `name` property
builder: (context, name) {
return Text('Name: $name');
},
);
ViewModelSelector<MyViewModel, int>(
viewModel: myViewModel,
selector: (vm) => vm.counter, // Watch only the `counter` property
builder: (context, counter) {
return Text('Counter: $counter');
},
);
Benefits:
- Ensures UI efficiency by rebuilding only the widgets affected by state changes.
- Simple and clean integration into existing
ChangeNotifier
architectures. - Great for modularizing UI updates when working with complex ViewModels.
Examples 🎯 #
Counter Example #
final incrementCommand = Command<int, NoParams>((_) async {
// Increment logic
await Future.delayed(Duration(seconds: 1));
return Success(42);
});
Timer Example #
final timerCommand = StreamCommand<int, NoParams>((_) {
return Stream.periodic(Duration(seconds: 1), (x) => 30 - x - 1)
.take(30)
.map((value) => Success(value));
});
timerCommand.start(const NoParams());
Full Implementation #
Check the example folder for a full implementation of both the counter and timer examples. Includes UI integration and detailed logic.
Additional Resources 📚 #
Learn more about the Command pattern and its practical applications:
- Architecting Flutter Apps the Right Way: A Practical Guide with Command Pattern and MVVM
- Flutter's Official Case Study on App Architecture
Error Handling 🚦 #
Convert all errors into FailureResult
instances:
final safeCommand = Command<int, int>((param) async {
try {
if (param < 0) throw Exception('Negative param');
return Success(param * 2);
} catch (e, s) {
return FailureResult<int>(
Failure(message: 'Error processing param', exception: e, stackTrace: s),
);
}
});
Testing 🧪 #
Commandy is test-ready:
- Mock commands to return
Success
orFailureResult
and verify behavior. - Use
Stream.fromIterable
for testingStreamCommand
. - Integrate
CommandListener
in UI tests withflutter_test
.
Contributing 🤝 #
We welcome contributions! Feel free to open issues, suggest enhancements, or submit pull requests. Please follow our coding style and ensure all tests pass.
License 📄 #
This project is licensed under the MIT License - see the LICENSE file for details.