xiaohongshu
xiaohongshu
asyncredux-async-actions
Creates AsyncRedux (Flutter) asynchronous actions for API calls, database operations, and other async work.
Full skill instructions
An action becomes asynchronous when its reduce() method returns Future<AppState?>
instead of AppState?. Use this for database access, API calls, file operations, or any
work requiring await.
class FetchUser extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final user = await api.fetchUser();
return state.copy(user: user);
}
}
Unlike traditional Redux requiring middleware, AsyncRedux makes it simple: return a
Future and it works.
If the action is async (returns a Future) and changes the state (returns a non-null
state), the framework requires that, all execution paths contain at least
one await. Never declare Future<AppState?> if you don't actually await something.
// Simple async with await
Future<AppState?> reduce() async {
final data = await fetchData();
return state.copy(data: data);
}
// Using microtask (minimum valid await)
Future<AppState?> reduce() async {
await microtask;
return state.copy(timestamp: DateTime.now());
}
// Conditional - both paths have await
Future<AppState?> reduce() async {
if (state.needsRefresh) {
return await fetchAndUpdate();
}
else return await validateCurrent();
}
// Always returns null
Future<AppState?> reduce() async {
if (state.needsRefresh) {
await fetchAndUpdate();
}
return null;
}
// WRONG: No await at all
Future<AppState?> reduce() async {
return state.copy(counter: state.counter + 1);
}
// WRONG: await only on some paths
Future<AppState?> reduce() async {
if (condition) {
return await fetchData();
}
return state; // No await on this path!
}
// WRONG: Calling async function without await
Future<AppState?> reduce() async {
someAsyncFunction(); // Not awaited
return state;
}
For complex reducers with multiple code paths, add assertUncompletedFuture() before the
final return. This catches violations at runtime during development:
class ComplexAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
if (state.cacheValid) {
// Complex logic that might accidentally skip await
return processCache();
}
final data = await fetchFromServer();
final processed = transform(data);
assertUncompletedFuture(); // Validates at least one await occurred
return state.copy(data: processed);
}
}
The state getter can change after every await because other actions may modify state
while yours is waiting:
class AsyncAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
print(state.counter); // e.g., 5
await someSlowOperation();
// state.counter might now be different (e.g., 10)
// if another action modified it during the await
print(state.counter);
return state.copy(counter: state.counter + 1);
}
}
Use initialState to access the state as it was when the action was dispatched (never
changes):
class SafeIncrement extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final originalCounter = initialState.counter;
await validateWithServer();
// Check if state changed while we were waiting
if (state.counter != originalCounter) {
// State was modified by another action
return null; // Abort our change
}
return state.copy(counter: state.counter + 1);
}
}
Use dispatch() when you don't need to wait for completion:
context.dispatch(FetchUser());
// Returns immediately, action runs in background
Use dispatchAndWait() to await the action's completion:
await context.dispatchAndWait(FetchUser());
// Continues only after action finishes AND state changes
print('User loaded: ${context.state.user.name}');
// Fire all, don't wait
context.dispatchAll([FetchUser(), FetchSettings(), FetchNotifications()]);
// Fire all and wait for all to complete
await context.dispatchAndWaitAll([FetchUser(), FetchSettings()]);
Use isWaiting() to show spinners while async actions run:
Widget build(BuildContext context) {
if (context.isWaiting(FetchUser)) return CircularProgressIndicator();
else return Text('Hello, ${context.state.user.name}');
}
Throw UserException for user-facing errors:
class FetchUser extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final response = await api.fetchUser();
if (response.statusCode == 404)
throw UserException('User not found.');
if (response.statusCode != 200)
throw UserException('Failed to load user. Please try again.');
return state.copy(user: response.data);
}
}
Check for failures in widgets:
Widget build(BuildContext context) {
if (context.isFailed(FetchUser)) {
return Text('Error: ${context.exceptionFor(FetchUser)?.message}');
}
// ...
}
// Async action with proper error handling
class LoadProducts extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
try {
final products = await api.fetchProducts();
return state.copy(products: products, productsLoaded: true);
} catch (e) {
throw UserException('Could not load products. Check your connection.');
}
}
}
// Widget showing all three states
Widget build(BuildContext context) {
// Loading state
if (context.isWaiting(LoadProducts)) {
return Center(child: CircularProgressIndicator());
}
// Error state
if (context.isFailed(LoadProducts)) {
return Center(
child: Column(
children: [
Text(context.exceptionFor(LoadProducts)?.message ?? 'Error'),
ElevatedButton(
onPressed: () => context.dispatch(LoadProducts()),
child: Text('Retry'),
),
],
),
);
}
// Success state
return ListView.builder(
itemCount: context.state.products.length,
itemBuilder: (_, i) => ProductTile(context.state.products[i]),
);
}
Never return FutureOr<AppState?> directly. AsyncRedux must know if the action is sync or
async:
// CORRECT
Future<AppState?> reduce() async { ... }
// CORRECT
AppState? reduce() { ... }
// WRONG - throws StoreException
FutureOr<AppState?> reduce() { ... }
URLs from the documentation:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience