/SampleDaggerForAndroid

Android 에서 Dagger 사용 법을 정리했습니다.

Primary LanguageJava

Android Dagger 활용하기

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-android dagger 에서 android Library 을 활용할 수 있는 라이브러리 입니다.
  • com.google.dagger:dagger-android-support dagger 에서 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

AndroidInjectdagger.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 에서 ComponentModule 을 구성하게 됩니다. 이유는 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);
}

SigletonScope 로 구성하였으며 Application 에서 inject 발생하므로 members-injection 메서드 구성 하였습니다. 또한 AndroidInjectionModule.classModules 로 설정하면서 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.FactoryMultiBinds 동작이 되므로 @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(생성자) 로 제공하고 있습니다. MainPresenterImplMainActivity 에 제공 하기 위해서 @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 구현 방법

FragmentActivity 안에서만 동작하므로 부모가 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 에서 AndroidInjectorinject 를 수행하려면 부모가 되는ActivityInjector를 생성해야 합니다. 생성 방법은 MainActivity 에서 설정하였던 방식과 동일합니다.

Fragment 에서 AndroidInjector 을 사용하여 inject을 수행하려면 Fragement 을 사용할 Activity에 Injector를 생성해야 합니다. Activity에 HasSupportFragmentInjectorimprement 하고 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 ModuleSubcomponent로 등록하고 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