Claude-skill-registry 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.
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-debugging" ~/.claude/skills/majiayu000-claude-skill-registry-asyncredux-debugging && rm -rf "$T"
skills/data/asyncredux-debugging/SKILL.mdDebugging 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}.
: Dispatch countD
: Rebuild countR
: Whether widget actually rebuiltRebuild
: The StoreConnector typeConnector
: ViewModel with state summaryModel
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
in your reducer to see state at that momentprint(state) - Check initialState: Access
to see state when action was dispatched (vs currentaction.initialState
)state - Use action status: Check
oraction.status.isCompletedOk
after dispatchaction.status.originalError - Conditional logging: Use
fromkDebugMode
to disable in productionpackage:flutter/foundation.dart - Override toString: Implement
on actions and state classes for better debug outputtoString()
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