All Shards

CommandShard

Run one-shot async actions (form submit, create/update/delete) with an idle → loading → data/error lifecycle.

CommandShard

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.

Core example

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.

Lifecycle & API

A CommandShard starts in AsyncIdle<Res>() — the action has not run yet. Calling execute:

  1. Emits AsyncLoading (retaining any previousData from a prior success via state.dataOrNull).
  2. On success, emits AsyncData(result).
  3. On failure, emits 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.
MemberDescription
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 isRunningWhether a run is in progress (state.isLoading).
Res? get valueOrNullThe 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);

Rendering with AsyncShardBuilder

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'),
)

Disable while running

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'),
  ),
)
Prefer naming intent over types? Subclass CommandShard<LoginForm, User> (for example SubmitCommand) and use that name as the ShardBuilder generic instead.

Next Steps

Next: ComputedShard. See also: the CommandShard Form example.