dire_di library

Dire DI - Spring-like Dependency Injection for Dart

A powerful dependency injection framework that brings Spring-like features to Dart, with Flutter compatibility using code generation instead of dart:mirrors.

Features

  • Spring-like Annotations: @Service, @Repository, @Component, @Controller, @UseCase, @DataSource
  • Constructor Injection: Automatic dependency resolution via constructors
  • Qualifier Support: Use named instances for specific bean selection
  • Singleton and Prototype Scopes: Control object lifecycle
  • Conditional Registration: @ConditionalOnProperty, @ConditionalOnClass
  • Profile Support: @Profile for environment-specific beans
  • Configuration Classes: @Configuration and @Bean for manual setup
  • Flutter Compatible: Uses code generation instead of dart:mirrors
  • Same API: Maintains the original DireContainer API you know and love

Quick Start

Add to your pubspec.yaml:

dependencies:
  dire_di: ^1.0.3

dev_dependencies:
  build_runner: ^2.4.7

Annotate your classes (same as before):

import 'package:dire_di/dire_di.dart';

@Service()
class UserService {
  final UserRepository userRepository;

  UserService(this.userRepository); // Constructor injection

  Future<User> getUser(int id) {
    return userRepository.findById(id);
  }
}

@Repository()
class UserRepository {
  Future<User> findById(int id) async {
    // Implementation here
  }
}

Run code generation:

flutter pub get
flutter pub run build_runner build

Use in your app (same API as before):

void main() async {
  final container = DireContainer();
  await container.scan(); // Now uses generated code instead of mirrors

  final userService = container.get<UserService>();
  // userService.userRepository is automatically injected!
}

Migration from Mirrors

If you're migrating from the old mirrors-based version:

  1. Change from field injection to constructor injection:

    // Before (mirrors)
    @Service()
    class UserService {
      @Autowired()
      late UserRepository userRepository;
    }
    
    // After (code generation)
    @Service()
    class UserService {
      final UserRepository userRepository;
      UserService(this.userRepository);
    }
    
  2. Add build_runner and run code generation:

    flutter pub add --dev build_runner
    flutter pub run build_runner build
    
  3. Same API - No changes to your main() or container usage!

Advanced Usage

Named Instances (Qualifiers)

@Service()
@Qualifier('primary')
class PrimaryUserService extends UserService {
  // Implementation
}

// Usage - same as before
final primary = container.get<UserService>('primary');

Singletons vs Prototype

@Service()
@Singleton() // Single instance
class DatabaseConnection {
  // Implementation
}

@Service()
@Prototype() // New instance each time
class RequestHandler {
  // Implementation
}

Environment-specific Beans

@Service()
@Profile('dev')
class DevUserService extends UserService {
  // Development implementation
}

@Service()
@Profile('prod')
class ProdUserService extends UserService {
  // Production implementation
}

Benefits of Code Generation

  • Flutter Compatible - No dart:mirrors dependency
  • Better Performance - No runtime reflection overhead
  • Compile-time Safety - Dependency errors caught at build time
  • Tree Shaking - Only used dependencies in final bundle
  • Same API - Your existing code mostly unchanged
  • IDE Support - Better autocomplete and refactoring
@Service()
@Qualifier('primary')
class PrimaryUserService implements UserService { }

@Service()
@Qualifier('secondary')
class SecondaryUserService implements UserService { }

@Component()
class UserController {
  @Autowired()
  @Qualifier('primary')
  late UserService userService;
}

Configuration Classes

@Configuration()
class DatabaseConfig {
  @Bean()
  @Singleton()
  Database createDatabase() {
    return Database(connectionString: 'localhost:5432');
  }
}

Profiles

@Service()
@Profile('development')
class DevUserService implements UserService { }

@Service()
@Profile('production')
class ProdUserService implements UserService { }

Classes

Autowired
Autowired annotation for automatic dependency injection
Bean
Bean method annotation
BeanDefinition
Represents a bean definition in the DI container
Component
Core component annotation - base for all stereotypes
Conditional
Base class for all conditional annotations
ConditionalOnBean
Register bean only if a bean of the specified type exists
ConditionalOnClass
Register bean only if the specified class is present on the classpath
ConditionalOnMissingBean
Register bean only if no bean of the specified type exists
ConditionalOnProperty
Register bean only if the specified property exists and matches the value
Configuration
Configuration class annotation
Controller
Controller annotation for marking web controllers and similar components. Controllers typically handle HTTP requests, user input, or other external interfaces.
DataSource
DataSource layer annotation - indicates data source components
DireContainer
The main dependency injection container for Dire DI
DireDiEntryPoint
Marks a file as the entry point for DI code generation.
FieldDefinition
Represents a field that needs injection
InjectionContext
Injection context for managing bean creation and dependency resolution
ParameterDefinition
Represents a constructor parameter that needs injection
Profile
Profile annotation for environment-specific beans
Prototype
Convenience annotation for prototype scope
Qualifier
Qualifier annotation for specific bean selection
Repository
Repository layer annotation - indicates data access layer components
Scope
Scope annotation for controlling bean lifecycle
Service
Service layer annotation - indicates that this class provides business logic
Singleton
Convenience annotation for singleton scope
UseCase
UseCase layer annotation - indicates application use case components

Enums

ScopeType
Enumeration of bean scope types

Mixins

DiCore
Mixin for StatefulWidget States that provides easy access to dependency injection.

Functions

areTypesEquivalent(Type type1, Type type2) bool
Checks if two types are equivalent for dependency injection purposes
convertStringToType<T>(String value, Type targetType) → T?
Converts a string to the appropriate type
getDefaultValue(Type type) Object?
Gets a default value for a type
getGenericTypeArguments(Type type) List<Type>
Extracts generic type arguments from a collection type
getNonNullableType(Type type) Type
Gets the non-nullable version of a type
getTypeName(Type type) String
Gets the simple name of a type (without library prefix)
isAssignableFrom(Type target, Type source) bool
Checks if a type is assignable from another type
isCollectionType(Type type) bool
Checks if a type is a collection type
isNullableType(Type type) bool
Checks if a type is nullable
isPrimitiveType(Type type) bool
Checks if a type is a primitive type

Exceptions / Errors

BeanCreationException
Exception thrown when bean creation fails
BeanNotFoundException
Exception thrown when a required bean cannot be found
CircularDependencyException
Exception thrown when circular dependencies are detected
ConditionalEvaluationException
Exception thrown when conditional evaluation fails
ContainerInitializationException
Exception thrown when container initialization fails
DireException
Base exception class for all Dire DI related exceptions
InjectionException
Exception thrown when dependency injection fails
InvalidBeanConfigurationException
Exception thrown when bean configuration is invalid
MultipleBeanFoundException
Exception thrown when multiple beans are found and no primary is specified