Agent skill
asyncredux-debugging
Debug AsyncRedux applications effectively. Covers printing state with store.state, checking actionsInProgress(), using ConsoleActionObserver, StateObserver for state change tracking, and tracking dispatchCount/reduceCount.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/asyncredux-debugging
SKILL.md
Debugging AsyncRedux Applications
AsyncRedux provides several tools for debugging and monitoring your application's state, actions, and behavior during development.
Inspecting Store State
Access the current state directly from the store:
// Direct state access
print(store.state);
// Access specific parts
print(store.state.user.name);
print(store.state.cart.items);
Tracking Actions in Progress
Use actionsInProgress() to see which actions are currently being processed:
// Returns an unmodifiable Set of actions currently running
Set<ReduxAction<AppState>> inProgress = store.actionsInProgress();
// Check if any actions are running
if (inProgress.isEmpty) {
print('No actions in progress');
} else {
for (var action in inProgress) {
print('Running: ${action.runtimeType}');
}
}
// Get a copy of actions in progress
Set<ReduxAction<AppState>> copy = store.copyActionsInProgress();
// Check if specific actions match
bool matches = store.actionsInProgressEqualTo(expectedSet);
Dispatch and Reduce Counts
Track how many actions have been dispatched and how many state reductions have occurred:
// Total actions dispatched since store creation
print('Dispatch count: ${store.dispatchCount}');
// Total state reductions performed
print('Reduce count: ${store.reduceCount}');
These counters are useful for:
- Verifying actions dispatched during tests
- Detecting unexpected dispatches
- Performance monitoring
Console Action Observer
The built-in ConsoleActionObserver prints dispatched actions to the console with color formatting:
var store = Store<AppState>(
initialState: AppState.initialState(),
// Only enable in debug mode
actionObservers: kReleaseMode ? null : [ConsoleActionObserver()],
);
Console output example:
I/flutter (15304): | Action MyAction
I/flutter (15304): | Action LoadUserAction(user32)
Actions appear in yellow (default) or green (for WaitAction and NavigateAction).
Customizing Action Output
Override toString() in your actions to display additional information:
class LoginAction extends AppAction {
final String username;
LoginAction(this.username);
@override
Future<AppState?> reduce() async {
// ...
}
@override
String toString() => 'LoginAction(username: $username)';
}
Custom Color Scheme
Customize the color scheme by modifying the static color callback:
ConsoleActionObserver.color = (action) {
if (action is ErrorAction) return ConsoleActionObserver.red;
if (action is NetworkAction) return ConsoleActionObserver.blue;
return ConsoleActionObserver.yellow;
};
Available colors: white, red, blue, yellow, green, grey, dark.
StateObserver for State Change Logging
Create a StateObserver to log state changes:
class DebugStateObserver implements StateObserver<AppState> {
@override
void observe(
ReduxAction<AppState> action,
AppState prevState,
AppState newState,
Object? error,
int dispatchCount,
) {
final changed = !identical(prevState, newState);
print('--- Action #$dispatchCount: ${action.runtimeType} ---');
print('State changed: $changed');
if (changed) {
// Log specific state changes
if (prevState.user != newState.user) {
print(' User changed: ${prevState.user} -> ${newState.user}');
}
if (prevState.counter != newState.counter) {
print(' Counter changed: ${prevState.counter} -> ${newState.counter}');
}
}
if (error != null) {
print(' Error: $error');
}
}
}
// Configure store
var store = Store<AppState>(
initialState: AppState.initialState(),
stateObservers: kDebugMode ? [DebugStateObserver()] : null,
);
Detecting State Changes
Use identical() to check if state actually changed:
bool stateChanged = !identical(prevState, newState);
This is efficient because AsyncRedux uses immutable state - if the reference is the same, no change occurred.
Custom ActionObserver for Detailed Logging
Create an ActionObserver for detailed dispatch tracking:
class DetailedActionObserver implements ActionObserver<AppState> {
final Map<ReduxAction, DateTime> _startTimes = {};
@override
void observe(
ReduxAction<AppState> action,
int dispatchCount, {
required bool ini,
}) {
if (ini) {
// Action started
_startTimes[action] = DateTime.now();
print('[START #$dispatchCount] ${action.runtimeType}');
} else {
// Action finished
final startTime = _startTimes.remove(action);
if (startTime != null) {
final duration = DateTime.now().difference(startTime);
print('[END #$dispatchCount] ${action.runtimeType} (${duration.inMilliseconds}ms)');
} else {
print('[END #$dispatchCount] ${action.runtimeType}');
}
}
}
}
Debugging Widget Rebuilds
Use ModelObserver with DefaultModelObserver to track which widgets rebuild:
var store = Store<AppState>(
initialState: AppState.initialState(),
modelObserver: DefaultModelObserver(),
);
Output format:
Model D:1 R:1 = Rebuild:true, Connector:MyWidgetConnector, Model:MyViewModel{data}.
Model D:2 R:2 = Rebuild:false, Connector:MyWidgetConnector, Model:MyViewModel{data}.
D: Dispatch countR: Rebuild countRebuild: Whether widget actually rebuiltConnector: The StoreConnector typeModel: ViewModel with state summary
Enable detailed output by passing debug: this to StoreConnector:
StoreConnector<AppState, MyViewModel>(
debug: this, // Enables connector name in output
converter: (store) => MyViewModel.fromStore(store),
builder: (context, vm) => MyWidget(vm),
)
Checking Action Status in Widgets
Use context extensions to check action states:
Widget build(BuildContext context) {
// Check if action is currently running
if (context.isWaiting(LoadDataAction)) {
return CircularProgressIndicator();
}
// Check if action failed
if (context.isFailed(LoadDataAction)) {
var exception = context.exceptionFor(LoadDataAction);
return Text('Error: ${exception?.message}');
}
return Text('Data: ${context.state.data}');
}
Waiting for Conditions in Tests
Use store wait methods for test debugging:
// Wait until state meets a condition
await store.waitCondition((state) => state.isLoaded);
// Wait for specific action types to complete
await store.waitAllActionTypes([LoadUserAction, LoadSettingsAction]);
// Wait for all actions to complete (empty list = wait for all)
await store.waitAllActions([]);
// Wait for action condition with access to actions in progress
await store.waitActionCondition((actionsInProgress, triggerAction) {
return actionsInProgress.isEmpty;
});
Complete Debug Setup Example
void main() {
final store = Store<AppState>(
initialState: AppState.initialState(),
// Action logging (debug only)
actionObservers: kDebugMode
? [ConsoleActionObserver(), DetailedActionObserver()]
: null,
// State change logging (debug only)
stateObservers: kDebugMode
? [DebugStateObserver()]
: null,
// Widget rebuild tracking (debug only)
modelObserver: kDebugMode ? DefaultModelObserver() : null,
// Error observer (always enabled)
errorObserver: MyErrorObserver(),
);
// Debug print initial state
if (kDebugMode) {
print('Initial state: ${store.state}');
print('Dispatch count: ${store.dispatchCount}');
}
runApp(StoreProvider<AppState>(
store: store,
child: MyApp(),
));
}
Debugging Tips
- Print state in actions: Use
print(state)in your reducer to see state at that moment - Check initialState: Access
action.initialStateto see state when action was dispatched (vs currentstate) - Use action status: Check
action.status.isCompletedOkoraction.status.originalErrorafter dispatch - Conditional logging: Use
kDebugModefrompackage:flutter/foundation.dartto disable in production - Override toString: Implement
toString()on actions and state classes for better debug output
References
URLs from the documentation:
- https://asyncredux.com/flutter/miscellaneous/logging
- https://asyncredux.com/flutter/miscellaneous/metrics
- https://asyncredux.com/flutter/miscellaneous/observing-rebuilds
- https://asyncredux.com/flutter/basics/store
- https://asyncredux.com/flutter/basics/dispatching-actions
- https://asyncredux.com/flutter/basics/wait-fail-succeed
- https://asyncredux.com/flutter/advanced-actions/action-status
- https://asyncredux.com/flutter/testing/dispatch-wait-and-expect
Didn't find tool you were looking for?