-
Dagger2 with AndroidAndroid 2023. 2. 14. 14:20
소개
이전에 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
- dagger-android
- 안드로이드 관련 클래스들을 지원
- dagger-android-support
- android-support 라이브러리를 사용하기 위해 지원
Application
보통 @Singleton Scope에 해당하는 모듈들을 관리하는 역할을 하며 안드로이드 컴포넌트들은(Activity..) 각각 고유의 라이프사이클을 가지고 있기 때문에 Application을 Injection을 수행하는 시작점으로 구성한다. 따라서 Dagger는 Application 단위에서 @Component를 구성하고 이하 Activity 등 다른 컴포넌트들을 @SubComponent로 구성하고 inject를 하는 것을 가이드로 주고 있습니.
@Inject가 일어나는 과정
- @Inject가 일어날 경우 우선 자신의 SubComponents를 돌며 해당하는 객체를 제공하는 Module를 찾습니다.
- 찾을경우 찾는 객체를 반환합니다.
- 찾지 못했을 경우 상위 Component로 올라가 1과 같은 작업을 진행합니다.
- 1-3을 반복합니다.
- 안드로이드용 Dagger를 사용하기 위해 AndroidInjectionModule를 애플리케이션 컴포넌트에 추가를 해야 합니다.
- @Singleton Scope에 포함되는 module들을 포함시켜 줍니다. ex) NetworkModule, RepositoryModule, RoomModule..
- 각 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을 통해 제공하는 로직을 넣어줍니다.
'Android' 카테고리의 다른 글
Android UI Test (0) 2023.02.14 Replace LiveData, SingleLiveEvent with Coroutines! (0) 2023.02.14 View Binding: What is it? (0) 2023.02.14 AAC의 ViewModel 사용 방법 정리 (0) 2023.02.14 RecyclerView (0) 2023.02.14 - Dagger2를 사용하기 위한 dependency 설정