Dagger2 with Android

Dagger2 with Android

소개

이전에 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'
  1. Dagger2를 사용하기 위한 dependency 설정

    • dagger
    • dagger-android

      • 안드로이드 관련 클래스들을 지원
    • dagger-android-support

      • android-support 라이브러리를 사용하기 위해 지원

Application

Dagger2%20with%20Android%206347f6223a1c49af8e0e4d3fa9dfe680/Untitled.png

보통 @Singleton Scope에 해당하는 모듈들을 관리하는 역할을 하며 안드로이드 컴포넌트들은(Activity..) 각각 고유의 라이프사이클을 가지고 있기 때문에 Application을 Injection을 수행하는 시작점으로 구성한다. 따라서 Dagger는 Application 단위에서 @Component를 구성하고 이하 Activity 등 다른 컴포넌트들을 @SubComponent로 구성하고 inject를 하는 것을 가이드로 주고 있습니.

@Inject가 일어나는 과정

  1. @Inject가 일어날 경우 우선 자신의 SubComponents를 돌며 해당하는 객체를 제공하는 Module를 찾습니다.
  2. 찾을경우 찾는 객체를 반환합니다.
  3. 찾지 못했을 경우 상위 Component로 올라가 1과 같은 작업을 진행합니다.
  4. 1-3을 반복합니다.
  5. 안드로이드용 Dagger를 사용하기 위해 AndroidInjectionModule를 애플리케이션 컴포넌트에 추가를 해야 합니다.
  6. @Singleton Scope에 포함되는 module들을 포함시켜 줍니다. ex) NetworkModule, RepositoryModule, RoomModule..
  7. 각 Activity에 해당하는 모듈 또한 포함시켜줍니다. MainActivityModule …
@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();
    }
}
  • @Component.Builder를 통해 AppComponent라는 컴포넌트의 빌더 클래스를 만들기 위한 interface를 정의합니다.
  • @BindsInstance를 통해 객체 그래프에 추가할 객체를 선언합니다.
  • 기본적으로 한 애플리케이션 안에 많은 Activity가 들어감으로 이러한 Activity를 따로 관리할 수 있는 ActivityBindingModule를 만들어 여러 Activity를 관리해 줍니다.
  • AndroidInjector를 통해 Application Instance를 inject하는 코드를 간소화 시킵니다.
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 및 Fragment

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);
}

@Singleton Scope에 해당하는 모듈들

@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을 통해 제공하는 로직을 넣어줍니다.

🌝mash-up Android🌚