xiaohongshu
xiaohongshu
asyncredux-dependency-injection
Inject dependencies into actions using the environment pattern. Covers creating an Environment class, passing it to the Store, accessing `env` from actions, and using dependency injection for testability.
Full skill instructions
AsyncRedux provides dependency injection through the Store's environment parameter. Dependencies stored in the environment are accessible throughout actions, widgets, and view-model factories, and are automatically disposed when the store is disposed.
Create an abstract class defining your injectable services:
abstract class Environment {
// Define your injectable services as abstract methods/getters
ApiClient get apiClient;
AuthService get authService;
Analytics get analytics;
int incrementer(int value, int amount);
int limit(int value);
}
Create concrete implementations for different contexts (production, staging, test):
class ProductionEnvironment implements Environment {
@override
ApiClient get apiClient => RealApiClient();
@override
AuthService get authService => FirebaseAuthService();
@override
Analytics get analytics => MixpanelAnalytics();
@override
int incrementer(int value, int amount) => value + amount;
@override
int limit(int value) => min(value, 100);
}
class TestEnvironment implements Environment {
@override
ApiClient get apiClient => MockApiClient();
@override
AuthService get authService => MockAuthService();
@override
Analytics get analytics => NoOpAnalytics();
@override
int incrementer(int value, int amount) => value + amount;
@override
int limit(int value) => value; // No limit in tests
}
When creating the store, pass your environment instance:
void main() {
var store = Store<AppState>(
initialState: AppState.initialState(),
environment: ProductionEnvironment(),
);
runApp(
StoreProvider<AppState>(
store: store,
child: MyApp(),
),
);
}
Extend ReduxAction to provide typed access to your environment:
/// Base action class with typed environment access
abstract class Action extends ReduxAction<AppState> {
@override
Environment get env => super.env as Environment;
}
Now use env in your actions:
class FetchUserAction extends Action {
final String userId;
FetchUserAction(this.userId);
@override
Future<AppState?> reduce() async {
// Access injected dependencies via env
final user = await env.apiClient.fetchUser(userId);
env.analytics.logEvent('user_fetched');
return state.copy(user: user);
}
}
class IncrementAction extends Action {
final int amount;
IncrementAction({required this.amount});
@override
AppState reduce() {
// Use environment methods in reducer
final newCount = env.incrementer(state.counter, amount);
return state.copy(counter: env.limit(newCount));
}
}
Create a BuildContext extension to access the environment in widgets:
extension BuildContextExtension on BuildContext {
AppState get state => getState<AppState>();
R select<R>(R Function(AppState state) selector) =>
getSelect<AppState, R>(selector);
Environment get env => getEnvironment<AppState>() as Environment;
}
Use in widgets:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Access environment
final env = context.env;
// Use environment logic in selectors
final counter = context.select((state) => env.limit(state.counter));
return Scaffold(
body: Center(
child: Text('Counter: $counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => dispatch(IncrementAction(amount: 1)),
child: const Icon(Icons.add),
),
);
}
}
The environment pattern makes testing straightforward by allowing you to inject test doubles:
void main() {
group('IncrementAction', () {
test('increments counter using environment', () async {
// Create store with test environment
var store = Store<AppState>(
initialState: AppState(counter: 0),
environment: TestEnvironment(),
);
await store.dispatchAndWait(IncrementAction(amount: 5));
// TestEnvironment has no limit, so value is 5
expect(store.state.counter, 5);
});
test('production environment limits counter', () async {
var store = Store<AppState>(
initialState: AppState(counter: 95),
environment: ProductionEnvironment(),
);
await store.dispatchAndWait(IncrementAction(amount: 10));
// ProductionEnvironment limits to 100
expect(store.state.counter, 100);
});
});
}
import 'dart:math';
import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';
late Store<int> store;
void main() {
store = Store<int>(
initialState: 0,
environment: EnvironmentImpl(),
);
runApp(MyApp());
}
/// Abstract environment interface
abstract class Environment {
int incrementer(int value, int amount);
int limit(int value);
}
/// Production implementation
class EnvironmentImpl implements Environment {
@override
int incrementer(int value, int amount) => value + amount;
@override
int limit(int value) => min(value, 5); // Limit counter at 5
}
/// Base action with typed env access
abstract class Action extends ReduxAction<int> {
@override
Environment get env => super.env as Environment;
}
/// Action using environment
class IncrementAction extends Action {
final int amount;
IncrementAction({required this.amount});
@override
int reduce() => env.incrementer(state, amount);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreProvider<int>(
store: store,
child: MaterialApp(home: MyHomePage()),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final env = context.env;
final counter = context.select((state) => env.limit(state));
return Scaffold(
appBar: AppBar(title: const Text('Dependency Injection Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Counter (limited to 5):'),
Text('$counter', style: const TextStyle(fontSize: 30)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => dispatch(IncrementAction(amount: 1)),
child: const Icon(Icons.add),
),
);
}
}
extension BuildContextExtension on BuildContext {
int get state => getState<int>();
R select<R>(R Function(int state) selector) => getSelect<int, R>(selector);
Environment get env => getEnvironment<int>() as Environment;
}
env getter provides compile-time checkingURLs 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