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.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/data/asyncredux-debugging/SKILL.md
source content

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 count
  • R
    : Rebuild count
  • Rebuild
    : Whether widget actually rebuilt
  • Connector
    : The StoreConnector type
  • Model
    : 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

  1. Print state in actions: Use
    print(state)
    in your reducer to see state at that moment
  2. Check initialState: Access
    action.initialState
    to see state when action was dispatched (vs current
    state
    )
  3. Use action status: Check
    action.status.isCompletedOk
    or
    action.status.originalError
    after dispatch
  4. Conditional logging: Use
    kDebugMode
    from
    package:flutter/foundation.dart
    to disable in production
  5. Override toString: Implement
    toString()
    on actions and state classes for better debug output

References

URLs from the documentation: