Claude-skill-registry 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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/asyncredux-events" ~/.claude/skills/majiayu000-claude-skill-registry-asyncredux-events && rm -rf "$T"
skills/data/asyncredux-events/SKILL.mdEvents 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:
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:
// 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:
// 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:
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:
// 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
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
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
- Created as spent: Events start as
in initial stateEvt.spent() - Dispatched as unspent: Action creates
orEvt()
and puts it in stateEvt<T>(value) - Widget rebuilds: State change triggers widget rebuild
- Consumed once:
returns the value and marks the event as spentcontext.event() - Returns null/false: Subsequent reads return
(typed) ornull
(boolean)false
Important Rules
Each Event Can Only Be Consumed by One Widget
If multiple widgets need the same trigger, create separate events:
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:
// 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:
// 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:
// 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: