.claude/skills/asyncredux-provider-integration/SKILL.md
Integrate AsyncRedux with the Provider package. Covers using provider_for_redux, the ReduxSelector widget, and choosing between StoreConnector and ReduxSelector approaches.
npx skillsauth add marcglasberg/async_redux asyncredux-provider-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The provider_for_redux package bridges Provider and AsyncRedux, enabling you to use Provider's dependency injection with Redux state management.
Add the package to your pubspec.yaml:
dependencies:
provider_for_redux: ^8.0.0
Replace StoreProvider with AsyncReduxProvider to expose three items to descendant widgets:
Store<AppState>)AppState)Dispatch)import 'package:provider_for_redux/provider_for_redux.dart';
void main() {
final store = Store<AppState>(initialState: AppState.initial());
runApp(
AsyncReduxProvider<AppState>.value(
value: store,
child: MaterialApp(home: MyHomePage()),
),
);
}
Access store components directly using standard Provider patterns:
// Access state (rebuilds on state changes)
final counter = Provider.of<AppState>(context).counter;
// Access dispatch (use listen: false for actions)
Provider.of<Dispatch>(context, listen: false)(IncrementAction());
// Access the store directly
final store = Provider.of<Store<AppState>>(context, listen: false);
ReduxConsumer provides store, state, and dispatch in a single builder, simplifying access:
ReduxConsumer<AppState>(
builder: (context, store, state, dispatch, child) {
return Column(
children: [
Text('Counter: ${state.counter}'),
Text('Description: ${state.description}'),
ElevatedButton(
onPressed: () => dispatch(IncrementAction()),
child: Text('Increment'),
),
],
);
},
)
ReduxSelector prevents unnecessary rebuilds by selecting specific state portions. Only when selected values change does the widget rebuild.
The simplest approach - explicitly list the properties that should trigger rebuilds:
ReduxSelector<AppState, dynamic>(
selector: (context, state) => [state.counter, state.description],
builder: (context, store, state, dispatch, model, child) {
return Column(
children: [
Text('Counter: ${state.counter}'),
Text('Description: ${state.description}'),
ElevatedButton(
onPressed: () => dispatch(IncrementAction()),
child: Text('Increment'),
),
],
);
},
)
For structured data, use a Tuple or custom class:
ReduxSelector<AppState, Tuple2<int, String>>(
selector: (context, state) => Tuple2(state.counter, state.description),
builder: (context, store, state, dispatch, model, child) {
return Column(
children: [
Text('Counter: ${model.item1}'),
Text('Description: ${model.item2}'),
ElevatedButton(
onPressed: () => dispatch(IncrementAction()),
child: Text('Increment'),
),
],
);
},
)
Both approaches manage widget rebuilds during state changes, but serve different use cases:
class MyCounterConnector extends StatelessWidget {
Widget build(BuildContext context) {
return StoreConnector<AppState, ViewModel>(
vm: () => Factory(this),
builder: (context, vm) => MyCounter(
counter: vm.counter,
description: vm.description,
onIncrement: vm.onIncrement,
),
);
}
}
class ViewModel extends Vm {
final int counter;
final String description;
final VoidCallback onIncrement;
ViewModel({
required this.counter,
required this.description,
required this.onIncrement,
}) : super(equals: [counter, description]);
}
ReduxSelector<AppState, dynamic>(
selector: (context, state) => [state.counter, state.description],
builder: (context, store, state, dispatch, model, child) {
return MyCounter(
counter: state.counter,
description: state.description,
onIncrement: () => dispatch(IncrementAction()),
);
},
)
Both Provider and AsyncRedux connectors work simultaneously, enabling gradual migration:
// Old code using StoreConnector continues to work
class OldFeatureConnector extends StatelessWidget {
Widget build(BuildContext context) {
return StoreConnector<AppState, OldViewModel>(
vm: () => OldFactory(this),
builder: (context, vm) => OldFeatureWidget(vm: vm),
);
}
}
// New code can use ReduxSelector
class NewFeatureWidget extends StatelessWidget {
Widget build(BuildContext context) {
return ReduxSelector<AppState, dynamic>(
selector: (context, state) => [state.newFeature],
builder: (context, store, state, dispatch, model, child) {
return NewFeatureContent(feature: state.newFeature);
},
);
}
}
This allows you to migrate incrementally without rewriting your entire application.
| Aspect | StoreConnector | ReduxSelector | |--------|---------------|---------------| | Boilerplate | More (ViewModel + Factory) | Less (inline selector) | | Separation | Smart/Dumb widget pattern | Single widget | | Testing | Easy to test UI in isolation | Requires store setup | | Provider compatibility | Native AsyncRedux | Full Provider integration | | Rebuild control | Via ViewModel equality | Via selector list |
URLs from the documentation:
data-ai
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
data-ai
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
testing
Handle user-facing errors with UserException. Covers throwing UserException from actions, setting up UserExceptionDialog, customizing error dialogs with `onShowUserExceptionDialog`, and using UserExceptionAction for non-interrupting error display.
tools
Implement undo/redo functionality using state observers. Covers recording state history with stateObserver, creating a RecoverStateAction, implementing undo for the full state or partial state, and managing history limits.