.claude/skills/asyncredux-action-status/SKILL.md
Checks an AsyncRedux (Flutter) action's completion status using ActionStatus right after the dispatch returns. Use only when you need to know whether an action completed, whether it failed with an error, what error it produced, or how to navigate based on success or failure.
npx skillsauth add marcglasberg/async_redux asyncredux-action-statusInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The ActionStatus object provides information about whether an action completed successfully or encountered errors. It is returned by dispatchAndWait() and related methods.
Use dispatchAndWait() to get the status after an action completes:
var status = await dispatchAndWait(MyAction());
From within an action, you can also use:
var status = await dispatchAndWait(SomeOtherAction());
isCompleted: Returns true if the action has finished executing (whether successful or failed)isCompletedOk: Returns true if the action finished without errors in both before() and reduce() methodsisCompletedFailed: Returns true if the action encountered errors (opposite of isCompletedOk)originalError: The error originally thrown by before() or reduce(), before any modificationwrappedError: The error after processing by the action's wrapError() methodThese properties track which lifecycle methods have completed:
hasFinishedMethodBefore: Returns true if the before() method completedhasFinishedMethodReduce: Returns true if the reduce() method completedhasFinishedMethodAfter: Returns true if the after() method completedNote: The execution tracking properties are primarily meant for testing and debugging. In production code, focus on isCompletedOk and isCompletedFailed.
The most common production use is checking if an action succeeded before navigating:
// In a widget callback
Future<void> _onSavePressed() async {
var status = await context.dispatchAndWait(SaveFormAction());
if (status.isCompletedOk) {
Navigator.pop(context);
}
}
Another example with push navigation:
Future<void> _onLoginPressed() async {
var status = await context.dispatchAndWait(LoginAction(
email: emailController.text,
password: passwordController.text,
));
if (status.isCompletedOk) {
Navigator.pushReplacementNamed(context, '/home');
}
// If failed, the error will be shown via UserExceptionDialog
}
Use ActionStatus to verify that actions throw expected errors:
test('MyAction fails with invalid input', () async {
var store = Store<AppState>(initialState: AppState.initial());
var status = await store.dispatchAndWait(MyAction(value: -1));
expect(status.isCompletedFailed, isTrue);
expect(status.wrappedError, isA<UserException>());
expect((status.wrappedError as UserException).msg, "Value must be positive");
});
test('SaveAction completes successfully', () async {
var store = Store<AppState>(initialState: AppState.initial());
var status = await store.dispatchAndWait(SaveAction(data: validData));
expect(status.isCompletedOk, isTrue);
expect(store.state.saved, isTrue);
});
When your action uses wrapError() to transform errors, you can inspect both:
class MyAction extends AppAction {
@override
Future<AppState?> reduce() async {
throw Exception('Network error');
}
@override
Object? wrapError(Object error, StackTrace stackTrace) {
return UserException('Could not save. Please try again.');
}
}
// In test:
var status = await store.dispatchAndWait(MyAction());
expect(status.originalError, isA<Exception>()); // The original Exception
expect(status.wrappedError, isA<UserException>()); // The wrapped UserException
The action lifecycle runs in this order:
before() - Runs first, can be used for preconditionsreduce() - Runs second (only if before() succeeded)after() - Runs last, always executes (like a finally block)The isCompletedOk property is true only if both before() and reduce() completed without errors. Note that errors in after() do not affect isCompletedOk.
If before() throws an error, reduce() will not run, but after() will still execute.
Use state changes for UI updates: In production, prefer checking state changes rather than action status. Reserve ActionStatus for cases where you need to perform side effects (like navigation) based on success/failure.
Use isCompletedOk for navigation: The common pattern is to navigate only after an action succeeds:
if (status.isCompletedOk) Navigator.pop(context);
Use wrappedError in tests: When testing error handling, check wrappedError to see what the user will actually see (after wrapError() processing).
Use originalError for debugging: When you need to see the underlying error before any transformation, use originalError.
URLs from the documentation:
data-ai
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
data-ai
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
testing
Handle user-facing errors with UserException. Covers throwing UserException from actions, setting up UserExceptionDialog, customizing error dialogs with `onShowUserExceptionDialog`, and using UserExceptionAction for non-interrupting error display.
tools
Implement undo/redo functionality using state observers. Covers recording state history with stateObserver, creating a RecoverStateAction, implementing undo for the full state or partial state, and managing history limits.