Command Types
Learn about command_it's factory function pattern for creating commands. Understanding this pattern makes it easy to choose the right factory for your needs.
Why Factory Functions?
Commands use static factory functions instead of a single constructor because:
Type safety - Each factory accepts exactly the right function signature:
createAsync<String, List<Todo>>takesFuture<List<Todo>> Function(String)createAsyncNoParam<List<Todo>>takesFuture<List<Todo>> Function()- No risk of passing the wrong function type
Implementation simplicity - Different function signatures (0-2 parameters, void/non-void returns) would require multiple optional function parameters in a constructor, making it error-prone and confusing
Clear intent - The factory name tells you exactly what you're creating:
createAsyncNoParam<List<Todo>>is clearer thanCommand<void, List<Todo>>()
The Naming Pattern
All 12 factory functions follow a simple formula:
create + [Sync|Async|Undoable] + [NoParam] + [NoResult]How to read the names:
- Sync/Async/Undoable - What kind of command (always present)
- NoParam - If present, command takes NO parameters
- NoResult - If present, command returns void
- Omitted parts - If "NoParam" or "NoResult" is missing, that feature IS present
Examples:
| Factory Name | Has Parameter? | Has Result? | Type |
|---|---|---|---|
createAsync<TParam, TResult> | ✅ Yes (TParam) | ✅ Yes (TResult) | Async |
createAsyncNoParam<TResult> | ❌ No (void) | ✅ Yes (TResult) | Async |
createAsyncNoResult<TParam> | ✅ Yes (TParam) | ❌ No (void) | Async |
createSyncNoParamNoResult | ❌ No (void) | ❌ No (void) | Sync |
createUndoable<TParam, TResult, TUndoState> | ✅ Yes (TParam) | ✅ Yes (TResult) | Undoable |
Key insight: If "NoParam" or "NoResult" appears in the name, that feature is ABSENT. If omitted, it's PRESENT.
NoResult Commands Still Notify Listeners
Even though NoResult commands return void, they still notify listeners when they complete successfully. This means you can watch them to trigger UI updates, navigation, or other side effects.
final saveCommand = Command.createAsyncNoResult<Data>(
(data) async => await api.save(data),
);
// You can still listen to completion
saveCommand.listen((_, __) {
showSnackbar('Saved successfully!');
});
// Or use with watch_it
watchValue(saveCommand, (_, __) {
// Called when save completes
});Commands are ValueListenable<TResult> where TResult is void for NoResult variants - the value doesn't change, but notifications still fire on execution.
Parameter Reference
Here's the complete signature of createAsync<TParam, TResult> - the most common factory function. All other factories share these same parameters (or a subset):
static Command<TParam, TResult> createAsync<TParam, TResult>(
Future<TResult> Function(TParam x) func, {
required TResult initialValue,
ValueListenable<bool>? restriction,
RunInsteadHandler<TParam>? ifRestrictedRunInstead,
bool includeLastResultInCommandResults = false,
ErrorFilter? errorFilter,
ErrorFilterFn? errorFilterFn,
bool notifyOnlyWhenValueChanges = false,
String? debugName,
})Required parameters:
func- The async function to wrap (positional parameter). Takes a parameter of typeTParamand returnsFuture<TResult>initialValue- The command's initial value before first execution (required named parameter). Commands areValueListenable<TResult>and need a value immediately. Not available for void commands (see NoResult variants below)
Optional parameters:
restriction-ValueListenable<bool>to enable/disable the command dynamically. Whentrue, command cannot execute. See RestrictionsifRestrictedRunInstead- Alternative function called when command is restricted (e.g., show login dialog). See RestrictionsincludeLastResultInCommandResults- Whentrue, keeps the last successful value visible inCommandResult.dataduring execution and error states. Default isfalse. See Command Results - includeLastResultInCommandResults for detailed explanation and use caseserrorFilter/errorFilterFn- Configure how errors are handled (local handler, global handler, or both). See Error HandlingnotifyOnlyWhenValueChanges- Whentrue, only notifies listeners if value actually changes. Default notifies on every executiondebugName- Identifier for logging and debugging, included in error messages
Variant Differences
All 12 factory functions use the same parameter pattern above, with these variations:
Sync vs Async:
- Sync commands (
createSync*):- Function parameter:
TResult Function(TParam)(regular function) - Execute immediately
- No
isRunningsupport (accessing it throws an exception)
- Function parameter:
- Async commands (
createAsync*):- Function parameter:
Future<TResult> Function(TParam)(returns Future) - Provide
isRunningtracking during execution
- Function parameter:
NoParam variants:
- Function signature has no parameter:
Future<TResult> Function()instead ofFuture<TResult> Function(TParam) ifRestrictedRunInsteadhas no parameter:void Function()instead ofRunInsteadHandler<TParam>
NoResult variants:
- Function returns
void:Future<void> Function(TParam)instead ofFuture<TResult> Function(TParam) - No
initialValueparameter (void commands don't need initial value) - No
includeLastResultInCommandResultsparameter (nothing to include)
Undoable commands:
- Covered in detail in the Undoable Commands section below
Undoable Commands
Undoable commands extend async commands with undo capability. They maintain an UndoStack<TUndoState> that stores state snapshots, allowing you to undo operations.
Complete signature:
static Command<TParam, TResult> createUndoable<TParam, TResult, TUndoState>(
Future<TResult> Function(TParam, UndoStack<TUndoState>) func, {
required TResult initialValue,
required UndoFn<TUndoState, TResult> undo,
bool undoOnExecutionFailure = true,
ValueListenable<bool>? restriction,
RunInsteadHandler<TParam>? ifRestrictedRunInstead,
bool includeLastResultInCommandResults = false,
ErrorFilter? errorFilter,
ErrorFilterFn? errorFilterFn,
bool notifyOnlyWhenValueChanges = false,
String? debugName,
})Required parameters:
func- Your async function that receives TWO parameters: the command parameter (TParam) AND the undo stack (UndoStack<TUndoState>) where you push state snapshots (positional parameter)initialValue- The command's initial value before first execution (required named parameter)undo- Handler function called to perform the undo operation (required named parameter):darttypedef UndoFn<TUndoState, TResult> = FutureOr<TResult> Function( UndoStack<TUndoState> undoStack, Object? reason )Pop state from the stack and restore it. Called when user manually undos or when
undoOnExecutionFailure: trueand execution fails
Optional parameters:
undoOnExecutionFailure- Whentrue(default), automatically calls the undo handler and restores state if the command fails. Perfect for optimistic updates that need rollback on error
Type parameters:
TParam- Command parameter type (same as regular commands)TResult- Return value type (same as regular commands)TUndoState- Type of state snapshot needed to undo the operation
Inherited parameters:
All other parameters (initialValue, restriction, errorFilter, etc.) work the same as in createAsync - see the Parameter Reference section above.
Additional methods:
undo()- Manually undo the last operation by calling the undo handler. The undo handler receives theUndoStackand can pop state to restore previous values
See also:
- Best Practices - Undoable Commands for practical examples
- Error Handling - Auto-Undo on Failure for error recovery patterns
See Also
- Command Basics - Detailed usage examples and patterns
- Command Properties - Understanding command state (value, isRunning, canRun, errors)
- Best Practices - When to use which factory and production patterns
- Error Handling - Error management strategies
- Restrictions - Dynamic command enable/disable patterns