티스토리 뷰

반응형



Dagger 의 기본 개념을 이해하고 있다는 것을 전재로 Android에서 Dagger 을 사용하는 방법을 공유하겠습니다. 만약 Dagger 개념에 대해서 이해 못하신 분은 Dagger 간단히 알아보기 포스트를 참고해주세요.


Android Studio에서 Dagger 사용 시 Dependency 을 설정해야 합니다.

//dagger2
implementation 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
implementation 'com.google.dagger:dagger-android:2.16'
implementation 'com.google.dagger:dagger-android-support:2.16'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
  • com.google.dagger:dagger-androiddagger 에서 android Library 을 활용할 수 있는 라이브러리 입니다.

  • com.google.dagger:dagger-android-supportdagger 에서 android support Library 을 활용할 수 있는 라이브러리 입니다.


안드로이드 프레임워크에서  Application 은 Singleton 으로서의 역할을 하며 컴포넌트들은 각각 고유의 라이프사이클을 기반으로 동작합니다. Application는 inject 를 수행하는 시작점이 됩니다. 따라서 Dagger 는 Application 단위에서 @Component 를 컴포넌트 단위에서 @Subcomponent 를 구성하고 inject 를 하는 것을 가이드로 주고 있습니다. 이것을 간단하게 하기 위해서 dagger.android 클래스들을 제공하고 있습니다.

안드로이드 컴포넌트

  • Application

  • Activity

  • Service

  • Content Provider

  • Broadcast


MainActivity Presenter DI 구현 방법

MVP 패턴 (Model-View-Presenter) 으로 구성한 MainActivity의 Presenter를 DI 구현하는 방법을 공유하겠습니다.

MainAcitivy

public class MainActivity extends AppCompatActivity implements MainActivityContract.View {

MainActivityContract.Presenter presenter;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
  }
}

Application는 component , MainActivity 는 subcomponent 을 구성하게 됩니다. MainActivity에서는 inject 역할을 담당해줄 @Subcompoent 와 외존성을 설정하는 @Module 을 구성합니다.

MainModule.class

@Module(subcomponents = MainFragmentComponent.class)
abstract class MainModule {
   @ActivityScope
   @Binds
   abstract MainActivityContract.View bindView(MainActivity activity);


   @ActivityScope
   @Binds
   public abstract MainActivityContract.Presenter bindPresenter(MainPresenterImpl mainPresenter);
}


ActivityScope.class

@Scope
public @interface ActivityScope {
}


MainComponent.class

@ActivityScope
@Subcomponent(modules = MainModule.class)
public interface MainComponent extends AndroidInjector<MainActivity> {
   @Subcomponent.Builder
   abstract class Builder extends AndroidInjector.Builder<MainActivity>{
  }
}

@Subcomponent annotation 을 붙여주고 의존성 설정을 위해 MainModule 을 설정합니다.MainComponent.class 에서 주의 깊게 살펴볼 것은 AndroidInject 를 상속하는 것입니다. AndroidInject 을 상속받으면서 interface로 MainComponent 을 구성하고 @Subcomponent.Builder 작성해주면 @Subcomponent 작성이 끝납니다.

AndroidInject

AndroidInject 는 dagger.android 에 포함하고 있는 클래스로 @Subcomponent 의 코드와 inject 절차를 간소화 합니다. 안드로이드에서는 프래임워크 LifeCycle 을 보면 앱 실행 시 Application 부터 시작하게 됩니다. 그렇기에 Application에서 @Component 를 구성하게 됩니다. @Subcomponent 인 Activity 에서 inject를 할 경우 Component 호출 -> SubComponent 빌드 -> inject 하는 코드들을 작성해야 합니다. 이것을 AndroidInjector.inject 한줄로 간소화 시켜줍니다.

  • AndroidInjector.inject 구성 안한 SubComponent 사용 예시

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}
  • AndroidInjector.inject 구성한 SubComponent 사용 예시

public class YourActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }
}


Application

Application 에서 Component 와 Module 을 구성하게 됩니다. 이유는 Application의 Lifecycle 에 따라 Component 는 Singleton 으로 관리 되어야하며 inject 또한 Application에서 이뤄지기 때문입니다. Module 안에 구성 또한 Application Lifecycle 을 적용하게 되어 Singleton으로 적용 됩니다.


AppComponent.class

@Singleton
@Component(modules = {
       AndroidInjectionModule.class,
       AppModule.class
})
public interface AppComponent {
   void inject(DaggerSampleApp daggerSampleApp);
}

Singleton 을 Scope 로 구성하였으며 Application 에서 inject 발생하므로 members-injection 메서드 구성 하였습니다. 또한 AndroidInjectionModule.class 을 Modules 로 설정하면서 dagger.android 에서 제공하는 Class을 사용할 수 있습니다.


AppModule.class

@Module(subcomponents = MainComponent.class)
abstract class AppModule {
   @Singleton
   @Binds
   abstract DataSource bindDataSource(DataSourceImpl dataSource);

   @Binds
   @IntoMap
   @ActivityKey(MainActivity.class)
   abstract AndroidInjector.Factory<? extends Activity>
   bindMainActivity(MainComponent.Builder builder);
}

Android 에서 AndroidInjector 사용을 위해서는 AndroidInject.Factory Binds 을 추가로 만들어줘야 합니다. Activity 는 해당하는 @Subcomponent 를 통해 inject 되므로 Subcomponent.builder 를 파라미터로 받는 바인터를 만들어 줍니다.AndroidInjector.Factory 는 MultiBinds 동작이 되므로 @ActivityKey(dagger.android) 를 지원하고 있습니다. (다른 컴포넌트들에 대한 key annotation 도 지원합니다. 예시. FragmentKey)


Application, MainActivity Inject

DaggerSampleApp.class

public class DaggerSampleApp extends Application implements HasActivityInjector{
   @Inject
   DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

   @Override
   public void onCreate() {
       super.onCreate();
       // inject
       DaggerAppComponent.create().inject(this);

  }

   @Override
   public AndroidInjector<Activity> activityInjector() {
       return dispatchingAndroidInjector;
  }
}

Application 에서 HasActivityInject 를 implement 해주면 AndroidInject<Activity> 를 리턴해주는 메서드를 작성할 수 있습니다. 리턴(return) 객체인 AndroidInject<Activity> 는 AndroidInjectModule 을 통해 바인딩(Binding) 할 수 있으며 이미 AppComponent 에서 modules 에 설정하여 install 을 하였습니다.

따라서 DispatchingAndroidInjector<Activity> 멤버변수를 만들고 @Inject 를 달아주면 inject 시에 Application Module 로 설정한 Activity 을 바인딩을 해줍니다. onCreate 에서 AppComponent 생성 후 inject 를 실행시켜주는 코드를 작성하면 Application 설정은 마무리가 됩니다.


MainActivity.class

public class MainActivity extends AppCompatActivity implements MainActivityContract.View {

   @Inject MainActivityContract.Presenter presenter;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       AndroidInjection.inject(this);
  }
}

MainActivity 에서는AndroidInjection.inject(this) 로 @Inject 멤버변수에 inject를 수행합니다.

이러한 한줄 코드로 바인딩(Binding) 해주는 것 때문에 dagger을 사용하지 않았나 예상이 되는데요. AndroidInject 로 DI 구성한 Activity을 바인딩(Binding) 할 경우 기본적으로 바인더(Binds)를 생성하게됩니다. 따라서 별도로 @Binds@Provider 설정하지 않아도 Activity 를 바인딩(Binding) 할 수 있습니다.


PresenterImpl 구성

객체를 제공하려면 @Provides 또는 @Inject constructor(생성자) 로 제공하고 있습니다. MainPresenterImpl 을 MainActivity 에 제공 하기 위해서 @Inject constructor(생성자) 을 사용하면 다음과 같습니다.

public class MainPresenterImpl implements MainActivityContract.Presenter {
   MainActivityContract.View view;
   DataSource dataSource;

   @Inject
   public MainPresenterImpl(MainActivityContract.View view, DataSource dataSource){
       this.view =view;
       this.dataSource = dataSource;
  }
}


MainFragment DI 구현 방법

Fragment 는 Activity 안에서만 동작하므로 부모가 Activity인 @Subcomponent 을 구성하게 됩니다. Fragment 도 Lifecycle 을 갖고 있기에 @FragmentScope 을 설정하였습니다.

FragmentScope.class

@Scope
public @interface FragmentScope {
}


MainFragmentComponent.class

@FragmentScope
@Subcomponent(modules = MainFragmentModule.class)
public interface MainFragmentComponent extends AndroidInjector<MainFragment> {
   @Subcomponent.Builder
   abstract class Builder extends AndroidInjector.Builder<MainFragment> {
  }
}

Fragment에서 AndroidInjector inject를 수행하려면 부모가 되는 ActivityInjector를 생성해야 합니다. 생성 방법은 MainActivity 에서 설정하였던 방식과 동일합니다.

Fragment 에서 AndroidInjector 을 사용하여 inject을 수행하려면 Fragement 을 사용할 Activity에 Injector를 생성해야 합니다. Activity에 HasSupportFragmentInjector 을 imprement 하고 DispatchingAndroidInjector 을 바인딩(Binding) 받아 리턴합니다.


MainActivity.class

public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector, MainActivityContract.View {

   @Inject MainActivityContract.Presenter presenter;
   @Inject DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;

   @Override
   public AndroidInjector<Fragment> supportFragmentInjector() {
       return fragmentDispatchingAndroidInjector;
  }
   //...
}


Activity Module에 Subcomponent로 등록하고 AndroidInjector.Factory 에 바인딩 해줄 Component.Builder을 설정합니다. Activity에서도 언급했듯이 AndroidInjector.Factory 은 MultiBinding 을 하기에 FragmentKey로 사용하여 Key를 설정합니다.

MainModule.class

@Module(subcomponents = MainFragmentComponent.class)
abstract class MainModule {
   @ActivityScope
   @Binds
   abstract MainActivityContract.View bindView(MainActivity activity);

   @ActivityScope
   @Binds
   public abstract MainActivityContract.Presenter bindPresenter(MainPresenterImpl mainPresenter);

   @Binds
   @IntoMap
   @FragmentKey(MainFragment.class)
   abstract AndroidInjector.Factory<? extends Fragment>
   bindMoviesFragment(MainFragmentComponent.Builder builder);
}

@FragmentKey 입력 시 주의사항

FragmentKeydagger.android.supportdagger.android 두 패키지에 모두 있습니다. 따라서 FragmentKey로 사용하려는 Fragment가 android.support.v4.app.Fragmentandroid.app.Fragment 패키지 중 소속되어 있는지 확인 후 알맞은 패키지를 import 해야 합니다.


Fragment Component 에서 AndroidSupportInjector을 사용하였기에 Fragment 에서는 inject 을 호출만으로 바인딩 할 수 있습니다.

public class MainFragment extends Fragment implements MainFragmentContract.View{

   @Inject MainFragmentContract.Presenter presenter;
 
   @Override
   public void onAttach(Context context) {
       AndroidSupportInjection.inject(this);
       super.onAttach(context);
  }
}


정리

DI 부터 시작해서 Dagger 와 Android Dagger에 대한 내용을 포스트 하였습니다. 의존성을 요청받으면 Subcomponent, Component, Inject 생성자 순으로 검색하여 주입하는 것을 확인 할 수 있습니다.

Dagger의 주요 기능은 Android Dager 에서 AndroidInjector 을 제공하여 간단하게 Inject 할 수 있는 것과 Scope 을 통하여 @AndroidScope, @FragmentScope 을 통하여 Lifecycle 에 맞춰서 설정할 수 있는 것을 알 수 있습니다.

Dagger 의 진입 장벽이 높지만 DI 을 통하여 개발한다면 유닛테스트 전환 및 유지보수에 도움이 될 것으로 판단됩니다. 하지만 진입 장벽이 높은 만큼 활용 범위는 고민해야 할 것으로 생각됩니다.

사용한 예제에 대한 소스코드를 참고하여 직접 작성해보시면 Android Dagger를 이해하는데 도움이 될 것으로 예상됩니다.

끝으로 Dagger을 이해하는데 잘 정리한 포스트를 제공해준 개발자 분들에게 감사의 뜻을 전합니다.


참고

Multi Module 과 Dagger2

DI, Dagger2 란?

DI(Dependency Injection) 와 Dagger2

DI 기본개념부터 사용법까지, Dagger2 시작하기

Dagger2 학습에 필요한 참고자료

Dagger Open Source

반응형
댓글