A fast dependency injector for Java and Android that generates plain Java source code without using reflection or runtime bytecode generation
—
Dagger provides several utility types and interfaces that enhance the dependency injection experience, offering lazy evaluation, members injection, and other advanced features.
Provides a handle to a lazily-computed value that is computed on the first call to get() and remembered for subsequent calls. Each Lazy<T> instance works independently.
/**
* Handle to lazily-computed value - computes on first get(), remembers result
*/
interface Lazy<T> {
/**
* Returns the underlying value, computing it if necessary
* @return the lazily-computed value
*/
T get();
}Key Features:
Usage Examples:
public class ExpensiveServiceClient {
private final Lazy<ExpensiveService> expensiveService;
private final RegularService regularService;
@Inject
public ExpensiveServiceClient(
Lazy<ExpensiveService> expensiveService,
RegularService regularService
) {
this.expensiveService = expensiveService;
this.regularService = regularService;
}
public void performOperation() {
// RegularService created immediately during injection
regularService.doSomething();
// ExpensiveService only created when first accessed
if (shouldUseExpensiveFeature()) {
expensiveService.get().performExpensiveOperation();
}
}
}
// Module providing lazy dependency
@Module
public class ServiceModule {
@Provides
@Singleton
ExpensiveService provideExpensiveService() {
// This won't be called until Lazy.get() is invoked
return new ExpensiveService();
}
}Injects dependencies into the fields and methods of existing instances. Used when Dagger cannot create the object directly but needs to inject dependencies into it.
/**
* Injects dependencies into fields and methods of instances
*/
interface MembersInjector<T> {
/**
* Injects dependencies into the given instance
* @param instance the instance to inject dependencies into
*/
void injectMembers(T instance);
}Key Features:
Usage Examples:
// Class with members injection
public class ExternallyCreatedActivity {
@Inject UserService userService;
@Inject DatabaseService databaseService;
private String activityData;
// Constructor not managed by Dagger
public ExternallyCreatedActivity(String data) {
this.activityData = data;
}
@Inject
void init(ConfigService configService) {
// Method injection - called after field injection
configure(configService.getActivityConfig());
}
}
// Component with members injection
@Component(modules = ServiceModule.class)
public interface ActivityComponent {
// Members injection method
void inject(ExternallyCreatedActivity activity);
// Alternative: return injected instance
ExternallyCreatedActivity inject(ExternallyCreatedActivity activity);
// Direct MembersInjector access
MembersInjector<ExternallyCreatedActivity> activityInjector();
}
// Usage
public class ActivityManager {
private final ActivityComponent component;
@Inject
public ActivityManager(ActivityComponent component) {
this.component = component;
}
public void setupActivity(String data) {
// Create instance externally
ExternallyCreatedActivity activity = new ExternallyCreatedActivity(data);
// Inject dependencies
component.inject(activity);
// Alternative using MembersInjector directly
MembersInjector<ExternallyCreatedActivity> injector =
component.activityInjector();
injector.injectMembers(activity);
}
}Standard JSR-330 interface for providing instances of a type. Every dependency in Dagger can be injected as Provider<T> to get new instances on each call.
/**
* JSR-330 interface for providing instances of a type
*/
interface Provider<T> {
/**
* Provides an instance of type T
* @return an instance of T
*/
T get();
}Key Features:
Usage Examples:
public class UserController {
private final Provider<UserSession> sessionProvider;
private final UserService userService;
@Inject
public UserController(
Provider<UserSession> sessionProvider,
UserService userService
) {
this.sessionProvider = sessionProvider;
this.userService = userService;
}
public void handleRequest() {
// Get fresh session for each request
UserSession session = sessionProvider.get();
userService.processRequest(session);
}
}
// Scoped vs Unscoped behavior
@Module
public class SessionModule {
// Unscoped: Provider.get() returns new instance each time
@Provides
UserSession provideUserSession() {
return new UserSession();
}
// Scoped: Provider.get() returns same instance within scope
@Provides
@Singleton
UserPreferences provideUserPreferences() {
return new UserPreferences();
}
}Dagger supports Optional<T> injection for dependencies that may or may not be present:
public class FeatureService {
private final CoreService coreService;
private final Optional<AnalyticsService> analyticsService;
@Inject
public FeatureService(
CoreService coreService,
Optional<AnalyticsService> analyticsService
) {
this.coreService = coreService;
this.analyticsService = analyticsService;
}
public void performFeature() {
coreService.execute();
// Analytics may or may not be available
analyticsService.ifPresent(analytics ->
analytics.trackFeatureUsage("feature_performed"));
}
}
// Module declaring optional binding
@Module
public abstract class OptionalModule {
@BindsOptionalOf
abstract Optional<AnalyticsService> optionalAnalytics();
}Dagger supports combinations of lazy evaluation and provider patterns:
public class ComplexDependencyClient {
private final Lazy<Provider<ExpensiveService>> lazyProvider;
private final Provider<Lazy<AnotherService>> providerLazy;
@Inject
public ComplexDependencyClient(
Lazy<Provider<ExpensiveService>> lazyProvider,
Provider<Lazy<AnotherService>> providerLazy
) {
this.lazyProvider = lazyProvider;
this.providerLazy = providerLazy;
}
public void complexOperation() {
// Lazy<Provider<T>>: Lazy evaluation of provider itself
Provider<ExpensiveService> provider = lazyProvider.get();
ExpensiveService service1 = provider.get();
ExpensiveService service2 = provider.get(); // May be different instance
// Provider<Lazy<T>>: Provider of lazy instances
Lazy<AnotherService> lazy1 = providerLazy.get();
Lazy<AnotherService> lazy2 = providerLazy.get(); // Different lazy instances
}
}All utility types work with qualifiers:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Background {}
public class QualifiedUtilityExample {
@Inject
public QualifiedUtilityExample(
@Background Lazy<ExecutorService> backgroundExecutor,
@Background Provider<WorkerThread> backgroundWorkers,
@Background Optional<CacheService> backgroundCache
) {
// All utilities respect qualifiers
}
}Use Lazy for Expensive Objects:
// Good: Lazy evaluation for expensive creation
@Inject Lazy<DatabaseConnection> dbConnection;
// Avoid: Immediate creation if not always needed
@Inject DatabaseConnection dbConnection;Provider for Multiple Instances:
// Good: When you need fresh instances
@Inject Provider<UserSession> sessionProvider;
// Avoid: Provider when singleton behavior is desired
@Inject Provider<ApplicationConfig> configProvider; // Use direct injectionMembersInjector for Framework Integration:
// Good: Framework objects Dagger can't create
public void onCreate(Bundle savedInstanceState) {
component.inject(this); // Android Activity
}
// Avoid: MembersInjector for regular objects
// Use constructor injection insteadOptional for Truly Optional Dependencies:
// Good: Feature may not be available
@Inject Optional<AnalyticsService> analytics;
// Avoid: Required dependencies as Optional
@Inject Optional<DatabaseService> database; // Should be requiredInstall with Tessl CLI
npx tessl i tessl/maven-com-google-dagger--dagger