DI Generator Build
A Flutter package that automatically generates dependency injection code for your classes using GetIt.
What This Package Does
This package automatically creates dependency injection methods for any class you mark with the @AutoRegister annotation. It generates .g.dart files that contain methods to register and retrieve your classes from GetIt.
Key Benefit: Dependencies are only created and registered when you actually need them, not at startup!
How Lazy Loading Works
Traditional Dependency Injection (Eager Loading):
void main() {
// β All services created immediately at startup
registerUserService(); // Creates UserService now
registerProductService(); // Creates ProductService now
registerPaymentService(); // Creates PaymentService now
// App starts slow, uses more memory from the beginning
}
With This Package (Lazy Loading):
void main() {
// β
No services created at startup - fast startup!
// Services exist only when you actually request them
}
void showUserProfile() {
// Only now is UserService created and registered
final userService = getUserService(); // Created on-demand! π―
// ProductService and PaymentService still don't exist
}
void showProducts() {
// Only now is ProductService created and registered
final productService = getProductService(); // Created on-demand! π―
}
Benefits of Lazy Loading
- π Faster App Startup: No waiting for unused services to initialize
- πΎ Memory Efficient: Services exist only when needed
- π― On-Demand Creation: Dependencies created exactly when required
- β‘ Better Performance: App feels snappy and responsive
- π Smart Resource Management: Resources allocated only when necessary
β¨ Features
- Intuitive Annotations: Use clear annotations like
@Factory,@Singleton,@LazySingleton - Automatic Code Generation: Generates dependency injection methods automatically
- GetIt Integration: Seamlessly integrates with the GetIt service locator
- Async Support: Full support for async dependency initialization
- Performance Optimized: Efficient dependency resolution with GetIt integration
π Quick Start
1. Add Dependencies
Add to your pubspec.yaml:
dependencies:
get_it: ^8.2.0
di_generator_build: ^1.6.2
dev_dependencies:
build_runner: ^2.7.0
2. Annotate Your Classes
import 'package:di_generator_build/annotations.dart';
@RegisterSingleton()
class AppConfig {
final String apiUrl;
final String apiKey;
AppConfig({required this.apiUrl, required this.apiKey});
}
@RegisterLazySingleton()
class HttpClient {
final AppConfig _config;
HttpClient(this._config);
Future<String> get(String url) async {
// HTTP client implementation
}
}
@RegisterFactory()
class EmailService {
final HttpClient _httpClient;
final String _apiKey;
EmailService(this._httpClient, {required String apiKey});
Future<void> sendEmail(String to, String subject, String body) async {
// Email sending logic
}
}
3. Generate Code
Run the code generator:
dart run build_runner build
4. Use Generated Methods
import 'your_file.g.dart';
void main() {
// Get services using generated methods (no parameters needed)
final config = getConfigService();
final client = getNetworkService(); // Automatically gets ConfigService dependency
final authService = getAuthService(); // Gets NetworkService dependency automatically
}
π Available Annotations
Synchronous Annotations
- @RegisterFactory(): Creates new instance each time
- @RegisterSingleton(): Creates instance immediately and reuses it
- @RegisterLazySingleton(): Creates instance on first use, then reuses it
Asynchronous Annotations
- @RegisterAsyncFactory(): Creates new async instance each time
- @RegisterAsyncSingleton(): Creates async instance immediately and reuses it
- @RegisterAsyncLazySingleton(): Creates async instance on first use, then reuses it
π― Key Benefits
Performance Optimization
Lazy singletons create dependencies only when first requested, reducing startup time and memory usage.
Memory Efficiency
- Factories: Create new instances each time (useful for stateless services)
- Singletons: Reuse the same instance (useful for stateful services)
- Lazy Singletons: Create on first use, then reuse (best of both worlds)
Async Support
Handle complex initialization scenarios with async support for database connections, API clients, and more.
Clean Architecture
Separate dependency creation from business logic, making your code more testable and maintainable.
Automatic Dependency Resolution
Dependencies are automatically injected based on constructor parameters, reducing boilerplate code.
Cross-File Dependencies
When services depend on each other across different files, import the generated .g.dart files in your main application code:
// In your main.dart or di_setup.dart
import 'services/app_config.g.dart';
import 'services/http_client.g.dart';
import 'services/email_service.g.dart';
// ... import other generated files as needed
Automatic Parameter Handling
The generated methods automatically handle all parameters with sensible defaults:
- Required parameters: Use meaningful defaults based on parameter names (e.g.,
apiKeyβ"default-api-key") - Optional parameters: Use the default values defined in the constructor
- Dependencies: Automatically resolved from GetIt service locator
// No need to pass parameters - everything is handled automatically
final authService = getAuthService(); // Uses default values for apiKey, secretKey, tokenExpiry
final userService = await getUserService(); // All dependencies resolved automatically
Important Note About Cross-File Dependencies
The generated .g.dart files are independent and don't automatically import each other. This is by design to keep the package generic. You need to:
- Import all the necessary
.g.dartfiles in your main application code - Call the dependency methods with their required parameters before using dependent services
- The GetIt service locator will handle the dependency resolution
Example:
// In your main.dart or di_setup.dart
import 'services/config_service.g.dart';
import 'services/network_service.g.dart';
import 'services/auth_service.g.dart';
import 'services/storage_service.g.dart';
import 'services/user_repository.g.dart';
import 'services/user_service.g.dart';
import 'services/notification_service.g.dart';
void main() async {
// Set up dependencies in the correct order (no parameters needed)
final config = getConfigService();
final network = getNetworkService(); // Uses the registered ConfigService
final auth = getAuthService(); // Uses the registered NetworkService
final storage = await getStorageService(); // Uses default connection string
final userRepo = await getUserRepository(); // Uses the registered StorageService
final userService = await getUserService(); // Uses the registered UserRepository and AuthService
final notification = await getNotificationService(); // Uses the registered NetworkService
}
Best Practices for Dependency Management
- Order Matters: Always call dependencies before the services that depend on them
- Required Parameters: Provide required parameters when calling factory services
- Async Services: Use
awaitfor async services - Singleton vs Factory: Understand when to use each type:
- Singleton: Use for configuration, shared state
- Lazy Singleton: Use for expensive services that should be shared
- Factory: Use for services that need different parameters each time
π Examples
Basic Service
@RegisterSingleton()
class UserService {
final UserRepository _repository;
UserService(this._repository);
Future<User> getUser(String id) async {
return await _repository.findById(id);
}
}
Service with Parameters
@RegisterFactory()
class EmailService {
final String _apiKey;
final EmailProvider _provider;
EmailService(this._provider, [this._apiKey = 'default-key']);
Future<void> sendEmail(String to, String subject, String body) async {
// Email sending logic
}
}
Async Service
@RegisterAsyncLazySingleton()
class DatabaseService {
final String _connectionString;
late final Database _database;
DatabaseService(this._connectionString);
Future<void> initialize() async {
_database = await Database.connect(_connectionString);
}
Future<QueryResult> query(String sql) async {
return await _database.execute(sql);
}
}
π Generated Code
The package automatically generates getter methods for each annotated class:
// For @RegisterSingleton() class UserService
UserService getUserService() {
return GetIt.instance.getOrRegister<UserService>(
() => UserService(getUserRepository()), RegisterAs.singleton);
}
// For @RegisterAsyncFactory() class DatabaseService
Future<DatabaseService> getDatabaseService({String connectionString = 'default'}) async {
return await GetIt.instance.getOrRegisterAsync<DatabaseService>(
() async => DatabaseService(connectionString), RegisterAs.factoryAsync);
}
π§ͺ Testing
The generated code integrates seamlessly with GetIt, making it easy to mock dependencies in tests:
void main() {
setUp(() {
// Register mock dependencies
GetIt.instance.registerSingleton<UserRepository>(MockUserRepository());
});
tearDown(() {
GetIt.instance.reset();
});
test('should get user service', () {
final userService = getUserService();
expect(userService, isA<UserService>());
});
}
π¦ Installation
dart pub add di_generator_build
dart pub add build_runner --dev
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Setup
- Clone the repository
- Install dependencies:
dart pub get - Run tests:
dart test - Make your changes
- Submit a pull request
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments
- Built on top of the excellent GetIt package
- Uses build_runner for code generation
- source_gen: Source code generation utilities
π Support
If you have any questions or need help, please:
- Check the examples directory
- Review the tests for usage patterns
- Open an issue on GitHub
- Check the documentation
Happy coding! π
Libraries
- annotations
- builder
- di_generator_build
- A powerful build runner package for automatic dependency injection code generation using GetIt in Flutter applications.
- get_it_extension