-
Notifications
You must be signed in to change notification settings - Fork 0
Dagger Mental Model
Dagger is an enormously useful library, but its documentation is not great, in my experience. Here is how I think about Dagger, for whatever that's worth, shared here in the hopes that it will be useful to others.
Dagger @Component
interfaces represent "holes" in your application's dependency graph. These holes declare the objects you need to obtain or inject.
@Component
public interface MyComponent {
MyService getMyService();
}
-
What it does: The
MyComponent
interface creates a "hole" forMyService
. Dagger needs to know how to provide an instance ofMyService
.
Modules define how to "fill" the holes in the dependency graph by providing the necessary objects.
@Module
public class MyModule {
@Provides
MyService provideMyService() {
return new MyService();
}
}
@Component(modules = MyModule.class)
public interface MyComponent {
MyService getMyService();
}
// Application Code:
public class Main {
public static void main(String[] args) {
MyComponent component = DaggerMyComponent.create();
MyService myService = component.getMyService();
System.out.println("Service: " + myService);
}
}
-
What it does: The
@Module
provides a "peg" (MyService
) that Dagger uses to fulfill the "hole" in the@Component
.
Modules can depend on values or objects that must be provided by other parts of the graph, creating their own "holes."
@Module
public class ConfigModule {
private final String configValue;
public ConfigModule(String configValue) {
this.configValue = configValue;
}
@Provides
String provideConfigValue() {
return configValue;
}
}
@Module
public class MyModule {
@Provides
MyService provideMyService(String configValue) {
return new MyService(configValue);
}
}
@Component(modules = {ConfigModule.class, MyModule.class})
public interface MyComponent {
MyService getMyService();
}
public class Main {
public static void main(String[] args) {
MyComponent component = DaggerMyComponent.builder()
.configModule(new ConfigModule("MyConfigValue"))
.build();
MyService myService = component.getMyService();
System.out.println("Service created with config: " + myService.getConfigValue());
}
}
Modules can depend on values provided by other modules, using factory methods for their creation.
@Module
public class DatabaseModule {
@Provides
Database provideDatabase() {
return new Database("localhost", 5432);
}
}
@Module
public class RepositoryModule {
@Provides
Repository provideRepository(Database database) {
return new Repository(database);
}
}
@Component(modules = {DatabaseModule.class, RepositoryModule.class})
public interface MyComponent {
Repository getRepository();
}
-
What it does:
DatabaseModule
provides aDatabase
, andRepositoryModule
depends on it to create aRepository
.
Modules can depend on values passed to their constructor, implementing Inversion of Control (IoC).
@Module
public class ConfigModule {
private final String apiUrl;
public ConfigModule(String apiUrl) {
this.apiUrl = apiUrl;
}
@Provides
String provideApiUrl() {
return apiUrl;
}
}
@Component(modules = ConfigModule.class)
public interface MyComponent {
ApiClient getApiClient();
@Component.Factory
interface Factory {
MyComponent create(ConfigModule configModule);
}
}
public class Main {
public static void main(String[] args) {
MyComponent component = DaggerMyComponent.factory()
.create(new ConfigModule("https://api.example.com"));
ApiClient apiClient = component.getApiClient();
System.out.println("API URL: " + apiClient.getApiUrl());
}
}
Objects provided by Dagger can also have their own dependencies (holes), which are filled by the graph.
public class MyService {
private final Dependency dependency;
@Inject
public MyService(Dependency dependency) {
this.dependency = dependency;
}
}
@Module
public class MyModule {
@Provides
Dependency provideDependency() {
return new Dependency();
}
}
@Component(modules = MyModule.class)
public interface MyComponent {
MyService getMyService();
}
-
What it does: Dagger resolves
Dependency
and injects it intoMyService
.
Subcomponents are used for parent-child lifecycle relationships. The relationship is explicitly declared at compile time. Controlling object lifecycles involves using scopes.
Component dependencies promote modularity by allowing components to depend on others without tightly coupling their implementation.
@Component
public interface LibraryComponent {
LibraryService getLibraryService();
}
@Component(dependencies = LibraryComponent.class)
public interface AppComponent {
AppService getAppService();
}
-
What it does: The
AppComponent
depends onLibraryComponent
to provide some of its dependencies.
Component dependencies can reuse object graphs for shared logic across components.
@Component
public interface SharedComponent {
Database getDatabase();
}
@Component(dependencies = SharedComponent.class)
public interface AppComponent {
WebResource getWebResource();
}
public class WebResource {
@Inject
public WebResource(Database database) {
}
}
Component dependencies allow "extending" functionality provided by another component.
A library might provide its own component (e.g., LibraryComponent
), and the application can make use of the object graph it provides.
@Component
public interface LibraryComponent {
LibraryService getLibraryService();
}
@Component(dependencies = LibraryComponent.class)
public interface ApplicationComponent {
AppService getAppService();
}
- Use Case: When building modular or library-based systems.