asyncredux-events
Use the Event class to interact with Flutter's stateful widgets (TextField, ListView, etc.). Covers creating Event objects in state, consuming events with `context.event()`, scrolling lists, changing text fields, and the event lifecycle.
Best use case
asyncredux-events is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use the Event class to interact with Flutter's stateful widgets (TextField, ListView, etc.). Covers creating Event objects in state, consuming events with `context.event()`, scrolling lists, changing text fields, and the event lifecycle.
Teams using asyncredux-events 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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/asyncredux-events/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How asyncredux-events Compares
| Feature / Agent | asyncredux-events | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use the Event class to interact with Flutter's stateful widgets (TextField, ListView, etc.). Covers creating Event objects in state, consuming events with `context.event()`, scrolling lists, changing text fields, and the event lifecycle.
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
# Events in AsyncRedux
Events are **single-use notifications** used to trigger side effects in widgets. They're designed for controlling native Flutter widgets like `TextField` and `ListView` that manage their own state through controllers.
## When to Use Events
Use events for:
- **Controller actions**: Clearing text, changing text, scrolling lists, focusing inputs
- **One-off UI actions**: Showing dialogs, snackbars, triggering animations
- **Implicit state changes**: Navigation, any action that should happen exactly once
Do NOT use events for:
- Values that need to be read multiple times (use regular state instead)
- Data that should be persisted (events should never be saved to local storage)
## Setup: Add context.event() Extension
Add the `event` method to your BuildContext extension:
```dart
extension BuildContextExtension on BuildContext {
AppState get state => getState<AppState>();
R select<R>(R Function(AppState state) selector) =>
getSelect<AppState, R>(selector);
// Add this for events:
R? event<R>(Evt<R> Function(AppState state) selector) =>
getEvent<AppState, R>(selector);
}
```
## Creating Events
### Boolean Events
For simple triggers that don't carry data:
```dart
// Create an unspent event (will return true once)
var clearTextEvt = Evt();
// Create a spent event (will return false)
var clearTextEvt = Evt.spent();
```
### Typed Events
For events that carry a value:
```dart
// Create an unspent event with a value (will return value once, then null)
var changeTextEvt = Evt<String>("New text");
var scrollToIndexEvt = Evt<int>(42);
// Create a spent event (will return null)
var changeTextEvt = Evt<String>.spent();
```
## Declaring Events in State
Initialize all events as **spent** in your initial state:
```dart
class AppState {
final Evt clearTextEvt;
final Evt<String> changeTextEvt;
final Evt<int> scrollToIndexEvt;
AppState({
required this.clearTextEvt,
required this.changeTextEvt,
required this.scrollToIndexEvt,
});
static AppState initialState() => AppState(
clearTextEvt: Evt.spent(),
changeTextEvt: Evt<String>.spent(),
scrollToIndexEvt: Evt<int>.spent(),
);
AppState copy({
Evt? clearTextEvt,
Evt<String>? changeTextEvt,
Evt<int>? scrollToIndexEvt,
}) => AppState(
clearTextEvt: clearTextEvt ?? this.clearTextEvt,
changeTextEvt: changeTextEvt ?? this.changeTextEvt,
scrollToIndexEvt: scrollToIndexEvt ?? this.scrollToIndexEvt,
);
}
```
## Dispatching Events from Actions
Actions create **unspent** events and place them in state:
```dart
// Boolean event - triggers clearing the text field
class ClearTextAction extends AppAction {
AppState reduce() => state.copy(clearTextEvt: Evt());
}
// Typed event - changes the text field to a new value
class ChangeTextAction extends AppAction {
final String newText;
ChangeTextAction(this.newText);
AppState reduce() => state.copy(changeTextEvt: Evt<String>(newText));
}
// Typed event from async operation
class FetchAndSetTextAction extends AppAction {
Future<AppState> reduce() async {
String text = await api.fetchText();
return state.copy(changeTextEvt: Evt<String>(text));
}
}
// Scroll to a specific index in a ListView
class ScrollToItemAction extends AppAction {
final int index;
ScrollToItemAction(this.index);
AppState reduce() => state.copy(scrollToIndexEvt: Evt<int>(index));
}
```
## Consuming Events in Widgets
Use `context.event()` in the widget's build method. **The event is consumed (marked as spent) immediately when read.**
### TextField Example
```dart
class MyTextField extends StatefulWidget {
@override
State<MyTextField> createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Consume the clear event - returns true once, then false
bool shouldClear = context.event((s) => s.clearTextEvt);
if (shouldClear) {
controller.clear();
}
// Consume the change event - returns the value once, then null
String? newText = context.event((s) => s.changeTextEvt);
if (newText != null) {
controller.text = newText;
}
return TextField(controller: controller);
}
}
```
### ListView Scrolling Example
```dart
class MyListView extends StatefulWidget {
@override
State<MyListView> createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
final scrollController = ScrollController();
final itemHeight = 50.0;
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final items = context.select((s) => s.items);
// Consume the scroll event
int? scrollToIndex = context.event((s) => s.scrollToIndexEvt);
if (scrollToIndex != null) {
// Schedule the scroll after the frame is built
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollController.animateTo(
scrollToIndex * itemHeight,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
return ListView.builder(
controller: scrollController,
itemCount: items.length,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text(items[index]),
),
);
}
}
```
## Event Lifecycle
1. **Created as spent**: Events start as `Evt.spent()` in initial state
2. **Dispatched as unspent**: Action creates `Evt()` or `Evt<T>(value)` and puts it in state
3. **Widget rebuilds**: State change triggers widget rebuild
4. **Consumed once**: `context.event()` returns the value and marks the event as spent
5. **Returns null/false**: Subsequent reads return `null` (typed) or `false` (boolean)
## Important Rules
### Each Event Can Only Be Consumed by One Widget
If multiple widgets need the same trigger, create separate events:
```dart
class AppState {
final Evt clearSearchEvt; // For search field
final Evt clearCommentsEvt; // For comments field
// ...
}
```
### Don't Use Events for Persistent Data
Events are mutable and designed for one-time use. Never persist them to local storage.
### Event Equality Prevents Unnecessary Rebuilds
Events have special equality methods that prevent unnecessary widget rebuilds when used correctly with the selector pattern.
## Advanced: Checking Event Status Without Consuming
Use these methods to check an event's status without consuming it:
```dart
// Check if an event has been consumed
bool consumed = myEvent.isSpent;
// Check if an event is ready to be consumed
bool ready = myEvent.isNotSpent;
// Get the underlying state without consuming
var eventState = myEvent.state;
```
## Advanced: Event.map() for Transformations
Transform an event's value:
```dart
// Map an event to a different type
Evt<String> nameEvt = Evt<int>(42).map((value) => 'Item $value');
```
## Advanced: Consuming from Multiple Event Sources
When you need to consume from multiple possible event sources:
```dart
// Create an event that consumes from first non-spent source
var combined = Event.from([event1, event2, event3]);
// Or use the static method
var value = Event.consumeFrom([event1, event2, event3]);
```
## References
URLs from the documentation:
- https://asyncredux.com/flutter/basics/events
- https://asyncredux.com/flutter/miscellaneous/advanced-events
- https://asyncredux.com/flutter/basics/using-the-store-state
- https://asyncredux.com/flutter/connector/store-connectorRelated Skills
asyncredux-wait-fail-succeed
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
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
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.
asyncredux-sync-actions
Creates AsyncRedux (Flutter) synchronous actions that update state immediately by implementing reduce() to return a new state.
asyncredux-streams-timers
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
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
Initialize, setup and configure AsyncRedux in a Flutter app. Use it whenever starting a new AsyncRedux project, or when the user requests.
asyncredux-selectors
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
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
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
Handle navigation through actions using NavigateAction. Covers setting up the navigator key, dispatching NavigateAction for push/pop/replace, and testing navigation in isolation.
asyncredux-dependency-injection
Inject dependencies into actions using the environment pattern. Covers creating an Environment class, passing it to the Store, accessing `env` from actions, and using dependency injection for testability.