Skip to content
asyncredux-dependency-injection logo

Dependency Injection with Environment

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.

SKILL.md

Full skill instructions

Dependency Injection with Environment

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.

Step 1: Define an Environment Interface

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);
}

Step 2: Implement the Environment

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
}

Step 3: Pass Environment to the Store

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(),
    ),
  );
}

Step 4: Access Environment from Actions

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));
  }
}

Step 5: Access Environment from Widgets

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),
      ),
    );
  }
}

Testing with Different Environments

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);
    });
  });
}

Complete Working Example

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;
}

Key Benefits

  • Testability: Swap implementations for testing without changing action code
  • Separation of concerns: Business logic lives in environment, actions orchestrate
  • Automatic disposal: Dependencies are disposed when the store is disposed
  • Type safety: The typed env getter provides compile-time checking
  • Scoped dependencies: Each store instance has its own environment, preventing test contamination

References

URLs from the documentation:

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

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...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

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...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

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.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View