이전에 Hilt에 대해서 소개해드렸는데 힐트 내부의 동작 방식이 어떻게 이루어지는지 잘 모르실 수도 있을 것 같아서 Dagger2가 어떻게 작동하고 어떤 방식으로 DI를 수행하는지 정리해 보았습니다.
build.gradle
//dagger2
implementation 'com.google.dagger:dagger:2.25'
kapt 'com.google.dagger:dagger-compiler:2.25'
implementation 'com.google.dagger:dagger-android:2.25'
implementation 'com.google.dagger:dagger-android-support:2.25'
kapt 'com.google.dagger:dagger-android-processor:2.25'
Dagger2를 사용하기 위한 dependency 설정
dagger-android
dagger-android-support
보통 @Singleton Scope에 해당하는 모듈들을 관리하는 역할을 하며 안드로이드 컴포넌트들은(Activity..) 각각 고유의 라이프사이클을 가지고 있기 때문에 Application을 Injection을 수행하는 시작점으로 구성한다. 따라서 Dagger는 Application 단위에서 @Component를 구성하고 이하 Activity 등 다른 컴포넌트들을 @SubComponent로 구성하고 inject를 하는 것을 가이드로 주고 있습니.
@Inject가 일어나는 과정
@Singleton
@Component(modules = {TasksRepositoryModule.class,
ApplicationModule.class,
ActivityBindingModule.class,
AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<ToDoApplication> {
@Component.Builder
interface Builder {
@BindsInstance
AppComponent.Builder application(Application application);
AppComponent build();
}
}
public interface AndroidInjector<T> {
/** Injects the members of {@code instance}. */
void inject(T instance);
.
.
.
}
ApplicationModule에는 context를 바인딩 할 수 있는 코드를 작성합니다.
@Module
public abstract class ApplicationModule {
//expose Application as an injectable context
@Binds
abstract Context bindContext(Application application);
}
Application 에서는 DaggerApplication을 상속 받아 AndroidInjector를 반환해 주는 코드를 작성합니다.
public class ToDoApplication extends DaggerApplication {
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}
public class TodoApplication extends Application(), HasAndroidInjector {
...
}
Activity는 기본적으로 Application의 하위 Component에 위치합니다. Application의 하위 그래프에 존재하므로 Custom Scope-@ActivityScoped를 통해 Activity를 관리할 수 있습니다.
@Module
public abstract class ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector(modules = TasksModule.class)
abstract TasksActivity tasksActivity();
@ActivityScoped
@ContributesAndroidInjector(modules = AddEditTaskModule.class)
abstract AddEditTaskActivity addEditTaskActivity();
@ActivityScoped
@ContributesAndroidInjector(modules = StatisticsModule.class)
abstract StatisticsActivity statisticsActivity();
@ActivityScoped
@ContributesAndroidInjector(modules = TaskDetailModule.class)
abstract TaskDetailActivity taskDetailActivity();
}
@ContributesAndroidInjector 어노테이션은 SubComponent와 해당 Module이 어떠한 메소드나 클래스를 사용하지 않을 경우 해당 어노테이션을 사용해 아래의 코드들을 자동적으로 생성해 줍니다.
위 어노테이션을 사용하지 않을 경우 추가해야 할 코드
// Auto Generated by using @ContributesAndroidInjector
@Subcomponent(modules = ...)
interface YourActivitySubcomponent : AndroidInjector<YourActivity> {
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<YourActivity> {}
}
@Module(subcomponents = YourActivitySubcomponent.class)
abstract class YourActivityModule: Module~??? {
@Binds
@IntoMap
@ClassKey(YourActivity.class)
abstract fun bindYourAndroidInjectorFactory(
YourActivitySubcomponent.Factory factory
): AndroidInjector.Factory<?>
}
@Component(modules = {..., YourActivityModule.class})
interface YourApplicationComponent {}
각 Activity의 Module에는 이제 그 하위의 위치할 FragmentComponent나 해당 Activity 내에서 의존성 주입이 필요한 객체들을 제공하는 로직이 들어갈 수 있습니다.
@Module
public abstract class AddEditTaskModule {
@Provides
@ActivityScoped
@Nullable
static String provideTaskId(AddEditTaskActivity activity) {
return activity.getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);
}
@Provides
@ActivityScoped
static boolean provideStatusDataMissing(AddEditTaskActivity activity) {
return activity.isDataMissing();
}
@FragmentScoped
@ContributesAndroidInjector
abstract AddEditTaskFragment addEditTaskFragment();
@ActivityScoped
@Binds
abstract AddEditTaskContract.Presenter taskPresenter(AddEditTaskPresenter presenter);
}
@Module
abstract public class TasksRepositoryModule {
private static final int THREAD_COUNT = 3;
@Singleton
@Binds
@Local
abstract TasksDataSource provideTasksLocalDataSource(TasksLocalDataSource dataSource);
@Singleton
@Binds
@Remote
abstract TasksDataSource provideTasksRemoteDataSource(FakeTasksRemoteDataSource dataSource);
@Singleton
@Provides
static ToDoDatabase provideDb(Application context) {
return Room.databaseBuilder(context.getApplicationContext(), ToDoDatabase.class, "Tasks.db")
.build();
}
C
@Singleton
@Provides
static TasksDao provideTasksDao(ToDoDatabase db) {
return db.taskDao();
}
@Singleton
@Provides
static AppExecutors provideAppExecutors() {
return new AppExecutors(new DiskIOThreadExecutor(),
Executors.newFixedThreadPool(THREAD_COUNT),
new AppExecutors.MainThreadExecutor());
}
}
DB나 Dao, DataSource 등을 @Singleton을 통해 제공하는 로직을 넣어줍니다.