ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Dagger2 with Android
    Android 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'
    1. Dagger2를 사용하기 위한 dependency 설정
      • dagger
      • dagger-android
        • 안드로이드 관련 클래스들을 지원
      • dagger-android-support
        • android-support 라이브러리를 사용하기 위해 지원

     

    Application

     

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

    '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
Designed by Tistory.