Dependency Injection (Dagger 2)
The previous lesson split RecipeBox into Presentation, Domain, and Data layers — but something still has to actually build a RecipeRepositoryImpl, hand it a RecipeDao and a RecipeApiService, wrap it in a SaveRecipeUseCase, and hand that to a ViewModel. Dagger 2 is a code-generation library that does this wiring automatically.
The Problem: Manual Wiring
Without DI, every Activity that needs a RecipeRepository ends up constructing the entire dependency chain by hand:
// Every Activity repeats this — and repeats it slightly differently over time
RecipeDao dao = AppDatabase.getInstance(this).recipeDao();
RecipeApiService api = RetrofitClient.getInstance().create(RecipeApiService.class);
RecipeRepository repository = new RecipeRepositoryImpl(dao, api);
SaveRecipeUseCase saveRecipeUseCase = new SaveRecipeUseCase(repository);
@Module and @Provides
A @Module is a class that tells Dagger how to build objects it can't construct with a plain constructor — like a Retrofit service or a Room database.
// di/AppModule.java
@Module
public class AppModule {
@Provides
@Singleton
RecipeDao provideRecipeDao(Application app) {
return AppDatabase.getInstance(app).recipeDao();
}
@Provides
@Singleton
RecipeApiService provideRecipeApiService() {
return RetrofitClient.getInstance().create(RecipeApiService.class);
}
@Provides
@Singleton
RecipeRepository provideRecipeRepository(RecipeDao dao, RecipeApiService api) {
return new RecipeRepositoryImpl(dao, api);
}
}
@Inject Constructor Injection
Classes RecipeBox owns outright — like SaveRecipeUseCase — don't need a @Module at all. Annotating the constructor with @Inject is enough for Dagger to know how to build one.
// domain/SaveRecipeUseCase.java
public class SaveRecipeUseCase {
private final RecipeRepository repository;
@Inject
public SaveRecipeUseCase(RecipeRepository repository) {
this.repository = repository;
}
public void execute(Recipe recipe) {
repository.save(recipe);
}
}
@Component: Wiring It Together
A @Component is the bridge between the modules and the Android classes that need the dependencies — it's what Dagger uses to generate the actual factory code at compile time.
// di/AppComponent.java
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
void inject(AddRecipeActivity activity);
}
Field Injection Into an Activity
// AddRecipeActivity.java
public class AddRecipeActivity extends AppCompatActivity {
@Inject
SaveRecipeUseCase saveRecipeUseCase;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((RecipeBoxApp) getApplication()).getAppComponent().inject(this);
setContentView(R.layout.activity_add_recipe);
// saveRecipeUseCase is already built and ready to use here
}
}
- Activities can't use constructor injection (the OS creates them), so Dagger falls back to field injection — annotate the field, then call inject(this).
- getAppComponent() typically lives on a custom Application subclass, built once when the app starts.
@Singleton Scope
Marking a @Provides method (or the whole @Component) with @Singleton tells Dagger to build that object once and hand back the same instance every time it's requested — exactly what's wanted for a shared RecipeRepository or database instance, instead of a fresh one per screen.
Manual DI vs. Dagger
| Manual wiring | Dagger 2 | |
|---|---|---|
| Boilerplate | Repeated in every Activity | Generated once, reused everywhere |
| Swapping for tests | Hard — real objects are hardcoded | Easy — provide a test module |
| Errors caught | At runtime (crash) | At compile time (build fails) |
| Learning curve | None | Steep — annotations, generated code, unfamiliar errors |
Hands-On Practice
- Add Dagger 2 to a RecipeBox module and create an AppModule providing RecipeDao and RecipeApiService.
- Annotate SaveRecipeUseCase's constructor with @Inject.
- Field-inject it into an Activity and confirm the app builds and runs unchanged.