CommandShard<Arg, Res> is a Shard<AsyncValue<Res>> for a single async action triggered explicitly via execute. Where FutureShard models a read that runs automatically, CommandShard models a write/action — a form submit, a create/update/delete, a "send" button.
It gives you an idle → running → success/failure lifecycle, a built-in double-submit guard, and automatic error capture, all without writing a bespoke shard.
See Async Values for the AsyncValue<Res> sealed class.
You construct a CommandShard with the action it runs, then call execute from a callback:
final submit = CommandShard<LoginForm, User>((form) => api.login(form));
// In a callback:
final user = await submit.execute(form); // returns User on success, null on failure
if (user != null) navigator.goHome();
A CommandShard plugs into ShardProvider and context.read like any other Shard.
A CommandShard starts in AsyncIdle<Res>() — the action has not run yet. Calling execute:
AsyncLoading (retaining any previousData from a prior success via state.dataOrNull).AsyncData(result).AsyncError — failures are also routed through addError.execute is ignored (returns null) if a run is already in progress (the double-submit guard, via state.isLoading) or if the shard has been disposed.| Member | Description |
|---|---|
Future<Res?> execute(Arg arg) | Runs the action. Returns the result on success, or null on failure or when ignored (already running / disposed). |
void reset() | Emits AsyncIdle<Res>(), returning to idle. After reset(), valueOrNull is null. |
bool get isRunning | Whether a run is in progress (state.isLoading). |
Res? get valueOrNull | The last success value (state.dataOrNull). null when idle, running, errored, or after reset(). |
For a no-arg action, use void as the argument type and call execute(null):
final logout = CommandShard<void, void>((_) => api.logout());
await logout.execute(null);
Because its state is an AsyncValue<Res>, a CommandShard renders with AsyncShardBuilder. Use onIdle: to handle the not-yet-run state:
AsyncShardBuilder<CommandShard<LoginForm, User>, User>(
shard: submit,
onIdle: (context) => const Text('Ready'),
onLoading: (context) => const CircularProgressIndicator(),
onData: (context, user) => Text('Welcome, ${user.name}'),
onError: (context, error, stackTrace) => Text('Failed: $error'),
)
The double-submit guard already prevents overlapping runs, but you typically also want to reflect the running state in the UI. Read the state directly with a ShardBuilder and disable the button while isLoading is true:
ShardBuilder<CommandShard<LoginForm, User>, AsyncValue<User>>(
shard: submit,
builder: (context, state) => ElevatedButton(
onPressed: state.isLoading ? null : () => submit.execute(form),
child: state.isLoading ? const Text('Submitting…') : const Text('Submit'),
),
)
CommandShard<LoginForm, User> (for example SubmitCommand) and use that name as the ShardBuilder generic instead.Next: ComputedShard. See also: the CommandShard Form example.