asyncredux-user-exceptions

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.

16 stars

Best use case

asyncredux-user-exceptions is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using asyncredux-user-exceptions should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/asyncredux-user-exceptions/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/design/asyncredux-user-exceptions/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/asyncredux-user-exceptions/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How asyncredux-user-exceptions Compares

Feature / Agentasyncredux-user-exceptionsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# UserException in AsyncRedux

`UserException` is a special error type for user-facing errors that should be displayed to the user rather than logged as bugs. These represent issues the user can address or should be informed about.

## Throwing UserException from Actions

Throw `UserException` when an action encounters a user-facing error:

```dart
class TransferMoney extends AppAction {
  final double amount;
  TransferMoney(this.amount);

  AppState? reduce() {
    if (amount == 0) {
      throw UserException('You cannot transfer zero money.');
    }
    return state.copy(cash: state.cash - amount);
  }
}
```

For async actions with validation:

```dart
class SaveUser extends AppAction {
  final String name;
  SaveUser(this.name);

  Future<AppState?> reduce() async {
    if (name.length < 4)
      throw UserException('Name must have at least 4 letters.');

    await saveUser(name);
    return null;
  }
}
```

## Converting Errors to UserException

Use `addCause()` to preserve the original error while showing a user-friendly message:

```dart
class ConvertAction extends AppAction {
  final String text;
  ConvertAction(this.text);

  Future<AppState?> reduce() async {
    try {
      var value = int.parse(text);
      return state.copy(counter: value);
    } catch (error) {
      throw UserException('Please enter a valid number')
        .addCause(error);
    }
  }
}
```

## Setting Up UserExceptionDialog

Wrap your home page with `UserExceptionDialog` below both `StoreProvider` and `MaterialApp`:

```dart
Widget build(context) {
  return StoreProvider<AppState>(
    store: store,
    child: MaterialApp(
      home: UserExceptionDialog<AppState>(
        child: MyHomePage(),
      ),
    ),
  );
}
```

If you omit the `onShowUserExceptionDialog` parameter, a default dialog appears with the error message and an OK button.

## Customizing Error Dialogs

Use `onShowUserExceptionDialog` to create custom error dialogs:

```dart
UserExceptionDialog<AppState>(
  onShowUserExceptionDialog: (BuildContext context, UserException exception) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Error'),
        content: Text(exception.message ?? 'An error occurred'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: Text('OK'),
          ),
        ],
      ),
    );
  },
  child: MyHomePage(),
)
```

For non-standard error presentation (like snackbars or banners), you can modify the behavior by accessing the `didUpdateWidget` method in a custom implementation.

## UserExceptionAction for Non-Interrupting Errors

Use `UserExceptionAction` to show an error dialog without throwing an exception or stopping action execution:

```dart
// Show error dialog without failing the action
dispatch(UserExceptionAction('Please enter a valid number'));
```

This is useful when you want to notify the user of an issue mid-action while continuing execution:

```dart
class ConvertAction extends AppAction {
  final String text;
  ConvertAction(this.text);

  Future<AppState?> reduce() async {
    var value = int.tryParse(text);
    if (value == null) {
      // Shows dialog but action continues
      dispatch(UserExceptionAction('Invalid number, using default'));
      value = 0;
    }
    return state.copy(counter: value);
  }
}
```

## Reusable Error Handling with Mixins

Create mixins to standardize UserException conversion across actions:

```dart
mixin ShowUserException on AppAction {
  String getErrorMessage();

  Object? wrapError(Object error, StackTrace stackTrace) {
    return UserException(getErrorMessage()).addCause(error);
  }
}

class ConvertAction extends AppAction with ShowUserException {
  final String text;
  ConvertAction(this.text);

  @override
  String getErrorMessage() => 'Please enter a valid number.';

  Future<AppState?> reduce() async {
    var value = int.parse(text); // Any error becomes UserException
    return state.copy(counter: value);
  }
}
```

## Global Error Handling with GlobalWrapError

Handle third-party or framework errors uniformly across all actions:

```dart
var store = Store<AppState>(
  initialState: AppState.initialState(),
  globalWrapError: MyGlobalWrapError(),
);

class MyGlobalWrapError extends GlobalWrapError {
  @override
  Object? wrap(Object error, StackTrace stackTrace, ReduxAction<dynamic> action) {
    if (error is PlatformException &&
        error.code == 'Error performing get') {
      return UserException('Check your internet connection')
        .addCause(error);
    }
    // Return the error unchanged for other cases
    return error;
  }
}
```

**Processing order**: Action's `wrapError()` -> `GlobalWrapError` -> `ErrorObserver`

## Error Queue

Thrown `UserException` instances are stored in a dedicated error queue within the store. The queue is consumed by `UserExceptionDialog` to display error messages. You can configure the maximum queue capacity in the Store constructor.

## Checking Failed Actions in Widgets

Use these methods to check action failure status and display errors inline:

```dart
Widget build(BuildContext context) {
  if (context.isFailed(SaveUserAction)) {
    var exception = context.exceptionFor(SaveUserAction);
    return Column(
      children: [
        Text('Failed: ${exception?.message}'),
        ElevatedButton(
          onPressed: () {
            context.clearExceptionFor(SaveUserAction);
            context.dispatch(SaveUserAction(name));
          },
          child: Text('Retry'),
        ),
      ],
    );
  }
  return Text('User saved successfully');
}
```

Note: Error states automatically clear when an action is redispatched, so manual cleanup before retry is usually unnecessary.

## Testing UserExceptions

Test that actions throw `UserException` correctly:

```dart
test('should throw UserException for invalid input', () async {
  var store = Store<AppState>(initialState: AppState.initialState());

  var status = await store.dispatchAndWait(TransferMoney(0));

  expect(status.isCompletedFailed, isTrue);
  var error = status.wrappedError;
  expect(error, isA<UserException>());
  expect((error as UserException).message, 'You cannot transfer zero money.');
});
```

Test multiple exceptions using the error queue:

```dart
test('should collect multiple UserExceptions', () async {
  var store = Store<AppState>(initialState: AppState.initialState());

  await store.dispatchAndWaitAll([
    InvalidAction1(),
    InvalidAction2(),
    InvalidAction3(),
  ]);

  var errors = store.errors;
  expect(errors.length, 3);
  expect(errors[0].message, 'First error message');
});
```

## References

URLs from the documentation:
- https://asyncredux.com/sitemap.xml
- https://asyncredux.com/flutter/advanced-actions/errors-thrown-by-actions
- https://asyncredux.com/flutter/basics/failed-actions
- https://asyncredux.com/flutter/testing/testing-user-exceptions
- https://asyncredux.com/flutter/basics/wait-fail-succeed
- https://asyncredux.com/flutter/advanced-actions/wrapping-the-reducer
- https://asyncredux.com/flutter/basics/store

Related Skills

design-and-user-experience-guidelines

16
from diegosouzapw/awesome-omni-skill

Specifies design and user experience guidelines, including dark mode compatibility, responsive design, performance optimization, modern UI, and accessibility. This rule promotes a user-friendly and vi

Clarify Epic/Feature/UserStory/Task ticketing guidance in SKILL

16
from diegosouzapw/awesome-omni-skill

No description provided.

asyncredux-wait-fail-succeed

16
from diegosouzapw/awesome-omni-skill

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.

asyncredux-wait-condition

16
from diegosouzapw/awesome-omni-skill

Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.

asyncredux-sync-actions

16
from diegosouzapw/awesome-omni-skill

Creates AsyncRedux (Flutter) synchronous actions that update state immediately by implementing reduce() to return a new state.

asyncredux-streams-timers

16
from diegosouzapw/awesome-omni-skill

Manage Streams and Timers with AsyncRedux. Covers creating actions to start/stop streams, storing stream subscriptions in store props, dispatching actions from stream callbacks, and proper cleanup with disposeProps().

asyncredux-state-access

16
from diegosouzapw/awesome-omni-skill

Access store state in widgets using `context.state`, `context.select()`, and `context.read()`. Covers when to use each method, setting up BuildContext extensions, and optimizing widget rebuilds with selective state access.

asyncredux-setup

16
from diegosouzapw/awesome-omni-skill

Initialize, setup and configure AsyncRedux in a Flutter app. Use it whenever starting a new AsyncRedux project, or when the user requests.

asyncredux-selectors

16
from diegosouzapw/awesome-omni-skill

Create and cache selectors for efficient state access. Covers writing selector functions, caching with `cache1` and `cache2`, the reselect pattern, and avoiding repeated computations in widgets.

asyncredux-persistence

16
from diegosouzapw/awesome-omni-skill

Implement local state persistence using Persistor. Covers creating a custom Persistor class, implementing `readState()`, `persistDifference()`, `deleteState()`, using LocalPersist helper, throttling saves, and pausing/resuming persistence with app lifecycle.

asyncredux-optimistic-update-mixin

16
from diegosouzapw/awesome-omni-skill

Add the OptimisticUpdate mixin for instant UI feedback before server confirmation. Covers immediate state changes, automatic rollback on failure, and optionally notifying users of rollback.

asyncredux-navigation

16
from diegosouzapw/awesome-omni-skill

Handle navigation through actions using NavigateAction. Covers setting up the navigator key, dispatching NavigateAction for push/pop/replace, and testing navigation in isolation.