본문 바로가기

[Android][Kotlin] Dagger2 #5 - Android Dagger 사용방법 3가지 (HasAndroidInjector, DaggerApplication, DispatchingAndroidInjector)

Android Dagger 사용방법 3가지

 

Dagger 관련 글


 

먼저 2번, 3번에서 사용되는 AndroidInjector를 사용하기 위해 Gradle 설정을 합니다

build.gradle (Module: app)
dependencies {
    def dagger2_version = '2.27'
    
    implementation "com.google.dagger:dagger:$dagger2_version"			// dagger 라이브러리
    implementation "com.google.dagger:dagger-android:$dagger2_version"		// dagger-android 라이브러리
    implementation "com.google.dagger:dagger-android-support:$dagger2_version"	// dagger-android-support 라이브러리
    // if you use the support libraries
    kapt "com.google.dagger:dagger-android-processor:$dagger2_version"
    kapt "com.google.dagger:dagger-compiler:$dagger2_version"
}

Dagger2는 크게 dagger / dagger-android / dagger-android-support 세 가지의 라이브러리로 구성됩니다

  • dagger-android
    : dagger에서 android Library를 활용할 수 있는 라이브러리
  • dagger-android-support
    : dagger에서 android support Library를 활용할 수 있는 라이브러리

2번 3번에서 사용할 HasAndroidInjector DaggerApplication DispatchingAndroidInjector AndroidInjector 들이 dagger-android 라이브러리로 제공해주기 때문에 Gradle 추가가 필요합니다

 

 

Android Dagger2 사용방법 3가지

  1. Component Module Scope를 모두 직접 기술해서 사용하는 방법
  2. DispatchingAdnroidInjector를 제공하는 방법
    : AndroidInjectionModule, HasAndroidInjector, DispatchingAndroidInjector
  3. 안드로이드 기반클래스(DaggerApplication, DaggerActivity, DaggerFragment 등)로 제공하는 방법
    : AndroidSupportInjectionModule, DaggerApplication, DaggerActivity, DaggerFragment

 

Component Module 직접 사용

MainActivity.kt
public class MainActivity : AppCompatActivity {

  @Inject
  lateinit var coffee : Coffee

  override fun onCreate(savedInstanceState : Bundle?) {
    super.onCreate(savedInstanceState)

    // 반드시 선행되어야 할 작업, 그렇지 않으면 coffee 멤버변수는 null
    var mainComponent: MainComponent = (application as MyApplication).appComponent
         .mainComponent()
         .create()

    // Member-Injection 유형의 Component함수로 coffee 멤버변수에 의존성 주입
    mainComponent.inject(this@MainActivity)

    // Provision 유형의 Component함수로 Coffee객체 반환(주입)
    var coffee2: Coffee = mainComponent.coffee()
  }
}

위 코드처럼 각 Activity Fragment 에서 의존성 주입을 위한 코드들을 필수적으로 작성해야 합니다.

위 방법은 작동은 제대로 하지만, 보일러 플레이트 코드의 증가로 왠만해서는 사용하지 않습니다

매번 의존성 주입을 위해 BaseApplication 클래스에서 AppComponent에게 각 Component를 받아와 Injection을 수행하는 필수적인 보일러 플레이트 코드의 증가는 프로젝트가 커지면 리팩토링 시 일일이 다 수정이 필요합니다 

 

HasAndroidInjector 구현

안드로이드용 Dagger는 1번과 같은 방법을 단순화해주는 방법을 제공합니다

안드로이드용 Dagger를 사용하기 위해 먼저 추가적으로 필요한 설정이 존재합니다

  1. AndroidInjectionModule 추가 (ApplicationComponent의 modules)
  2. ActivityBindingModule 추가 (ApplicationComponent의 modules)
  3. AndroidInjector<Activity 또는 Fragment> 상속 (SubComponent interface에 상속)
  4. AndroidInjector.Factory<Activity 또는 Fragment) 상속 (SubComponent.Factory interface에 상속)
  5. Application 단위 클래스에서 HasAndroidInjector를 상속하고 androidInjector() 메서드를 구현하기(DispatchingAndroidInjector를 반환)
  6. 각 Activity / Fragment에서 AndroidInjection.inject()로 의존성 주입을 완료기

조건을 하나씩 설정하겠습니다

 

AndroidInjectionModule 추가

ActivityBindingModule 추가

ApplicationComponent.interface
@Singleton
@Component(modules = [
    AndroidInjectionModule::class,	// 조건 1번,
    ActivityBindingModule::class, // 조건 2번, AppComponent의 하위 Component 연결위한 Module
    CoffeeModule::class 		// Coffee 의존성 인스턴스 생성 위한 Module
])
interface AppComponent {

    fun inject(myApp : MyApp)	// Application을 구현한 BaseApplication 클래스
}

 

AndroidInjector<>, AndroidInjector.Factory<> 상속

AndroidInjector.Factory<Activity 또는 Fragment) 상속

MainComponent.interface
@Subcomponent(modules=[...])
interface MainComponent : AndroidInjector<MainActivity> {

    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<MainActivity> {}
}

MainActivity에서 사용할 SubComponent인 MainComponent를 AndroidInjector<MainActivity>로 상속해서 구현했습니다.

이제 AppComponent와 부모-자식 관계를 설정하기 위해 ActivityBindingModule에 MultiBinding 설정하겠습니다

ActivityBindingModule.kt
@Module(subcomponents=[
    MainComponent::class,		// MainActivity Component
    DetailComponent::Class		// DetailActivity Component
])
abstract class ActivityBindingModule {	// @Binds 메서드 사용하기 때문에 abstract(추상) 설정

    @Binds
    @IntoMap				// MultiBinding을 위한 annotation
    @ClassKey(MainActivity::class)	// MultiBinding Map의 Key Type
    abstract fun bindMainInjectorFactory(factory: MainComponent.Factory) : AndroidInjector.Factory<*>

    @Binds
    @IntoMap
    @ClassKey(DetailActivity::class)
    abstract fun bindDetailInjectorFactory(factory: DetailComponent.Factory) : AndroidInjector.Factory<*>
}

ActivityBindingMoudle에 subcomponent 속성으로 MainComponent DetailComponent를 설정하여 AppComponent의 SubComponent로 관계를 설정하였습니다.

MainComponent.Factory 와 DetailComponent.Factory 인스턴스를 요청하게 되면 @Binds로 설정하였기 때문에 AndroidInjector.Facotry<*> 로 추상 캐스팅되어 인스턴스를 주입해주게 됩니다

@Binds@IntoMap으로 MultiBinding을 구현하였고, Factory를 반환하는 두 메서드의 반환타입은 아래 처럼 반환하게 됩니다

// Key : Class<*>, Value : AndroidInjecotr.Factory<*>
Map<Class<*>, AndroidInjector.Factory<*>>

SubComponent와 SubComponent.Factory가 메서드가 없고, 부모(상속)이 없을 경우
@ContributeAndroidInjector를 사용하기

위의 MainComponent 선언부분을 보면 Component와 Factory 내부 메서드가 따로 없습니다. 이런 경우엔 interface를 선언하지 않고 Module 내부에 @ContributesAndroidInjector 어노테이션을 사용한 추상메서드로 대체할 수 있습니다

ActivityBindingModule.kt
@Module
abstract class ActivityBindingModule {

    @ActivityScope				// Scope 설정 가능
    @ContributesAndroidInjector(modules=[...]) // modules 설정 가능
    abstract fun mainActivity() : MainActivity

    @ActivityScope				// Scope 설정 가능
    @ContributesAndroidInjector(modules=[...]) // modules 설정 가능
    abstract fun detailActivity() : DetailActivity
}

ActivityBinding을 하는 Module에 @ContributesAndroidInjector로 SubComponent들의 선언을 대체합니다.

이전에 작성했던 MainComponent DetailComponent의 interface 선언은 이제 필요없으므로 지웁니다. 자식 Component가 없으니 @Module 주석에 subcomponents 설정을 생략합니다

SubComponent interface 선언한 것 처럼 module 연결, Scope 설정 모두 가능합니다. 단 이렇게 사용할 경우
SubComponent에 메서드를 구현할 수 없다는 점을 기억해야합니다

이제 Dagger 구조는 모두 정리했으니 Application과 사용할 Activity에서 사용하면 됩니다

HasAndroidInjector 상속, androidInjector() 메서드 구현
(DispatchingAndroidInjector를 반환)

MyApp.kt
// HasAndroidInjector 인터페이스 상속
class MyApp : Application(), HasAndroidInjector {

	// AppComponent에게 DispatchingAndroidInjector 의존성 요청
    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

    override fun onCreate() {
        super.onCreate()
        // AppComponent의 Member-Injection 메서드 inject()
        DaggerAppComponent.create().inject(this@MyApp)
    }

    /** Returns an [AndroidInjector].  */
    // HasAndroidInjector 인터페이스 메서드 구현 (재정의)
    override fun androidInjector(): AndroidInjector<Any>? {
        return dispatchingAndroidInjector
    }
}

HasAndroidInjector 인터페이스를 Application 단위 클래스에서 상속하고 추상메서드를 구현합니다

DispatchingAndroidInjector는 AppComponent에 등록된 ActivityBindingModule로 각 SubComponent들의 AndroidInjector가 구현된 Component 인스턴스 같습니다

이제 마지막으로 사용을 원하는 Activity / Fragment에서 AndroidInjection.inject() 메서드만 사용해서 의존성 주입이 가능합니다

AndroidInjector.inject()로 의존성 주입

MainActivity.kt
class MainActivity : AppCompatActivity() {

    // 의존성 요청할 MultiBinding 타입
    @Inject
    lateinit var coffee: Map<Class<*>, @JvmSuppressWildcards Coffee>

    override fun onCreate(savedInstanceState: Bundle?) {
        // AndroidInjection.inject() 메서드 한번으로 Dagger 의존성 주입 (super.onCreate() 전에 사용 권장)
        AndroidInjection.inject(this@MainActivity)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        println(coffee[Espresso::class.java]?.name())
    }
}

이제 일반적인 Dagger 사용방법 처럼 긴 메서드 체이닝 방식을 사용하지 않고 AndroidInjection.inject() 메서드 만으로도 의존성 주입을 자동으로 완료합니다

 

DaggerApplication DaggerActivity 

이 방법은 안드로이드 기반클래스(DaggerApplication, DaggerActivity, DaggerFragment 등)을 사용해서 Dagger 의존성 주입을 하는 방법입니다

2번째 방법 HasAndroidInjection을 직접 구현하는 것과 원리는 같은 것 같은데, DaggerApplication DaggerActivity가 대신 귀찮은 부분을 해주는것 같습니다

2번 방법에서 조금 수정이 필요합니다

 

AppComponent 설정 (모듈 / 상속)

: AppComponent에 모듈(AndroidSupportInjectionModule) 설정과 AndroidInjector 인터페이스 상속

AppComponent.interface
@Component(modules = [
        AndroidSupportInjectionModule::class,	// 1️⃣ 
        ActivityBindingModule::class,
        CoffeeModule::class
])
interface AppComponent : AndroidInjector<MyApp> {	// 2️⃣
	// 3️⃣ 
}	

기존 2번 방법과 차이점입니다

1️⃣ AndroidSupportInjectionModule 설정 : 2번 HasAndroidInjection 직접 구현에는 AndroidInjectionModule 사용

2️⃣ AndroidInjector<> 상속 

3️⃣ Member-Injection Method inject() 제거 

이렇게 3개의 차이가 있습니다. AndroidInjector를 상속함으로 Member-Injection인 inject()함수를 없애고 위임합니다

 

DaggerApplication 상속/구현

MyApp.kt
class MyApp : DaggerApplication() {

    override fun onCreate() {
        super.onCreate()
    }

    // DaggerApplication의 메서드 구현(재정의) - AppComponent는 AndroidInjector를 상속했기에 반환Type에 캐스팅
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.create()	// AppComponent 인스턴스 반환
    }

}

2번의 방법보다 application 단위 클래스가 간단한 구조입니다.
단순히 기존 Application 상속을 DaggerApplication 상속으로 변경하고, DaggerApplication의 메서드인 applicationInjector()를 재정의 해주면 됩니다

applicationInjector() 메서드 반환Type은 AndroidInjector<out DaggerApplication>입니다.
그런데 return값을 보면 AppComponent의 인스턴스를 반환하고 있습니다.

이건 AppComponent 인터페이스에서AndroidInjector<MyApp>을 상속, MyApp = DaggerApplication 상속 조건으로
즉, out DaggerApplication(MyApp extends DaggerApplication)이 성립하게 됩니다

그러므로 AppComponent 인스턴스 => AndroidInjector<out DaggerApplication> 으로 추상화가 성립됩니다

2번 방법에선 DispatchingAndroidInjector와 androidInjector() 메서드 재정의 등, 이해가 어려운 부분이 많았지만 이 방법은 그 부분을 DaggerApplication으로 위임했기 때문에 저희가 구현하지 않아도 되므로 더 간편합니다
DaggerApplication 구조는 아래와 같습니다

DaggerApplication.class
@Beta
public abstract class DaggerApplication extends Application implements HasAndroidInjector {	// 1️⃣ 
  @Inject volatile DispatchingAndroidInjector<Object> androidInjector;	// 2️⃣ 

  @Override
  public void onCreate() {
    super.onCreate();		// 3️⃣ 
    injectIfNecessary();
  }

  @ForOverride
  protected abstract AndroidInjector<? extends DaggerApplication> applicationInjector(); // 4️⃣ 

  private void injectIfNecessary() {
   ... // 생략
  }

  @Override
  public AndroidInjector<Object> androidInjector() {	// 5️⃣ 
    injectIfNecessary();

    return androidInjector;
  }
}

DaggerApplication 구조를 하나씩 보겠습니다

1️⃣ DaggerApplication은 Application 클래스를 상속하고, HasAndroidInjector interface를 구현합니다
  : 2번 방법에서 Application 단위 클래스에서 Application 상속과 HasAndroidInjector 구현하던 것을 DaggerApplication이 대신하는 것을 의미합니다

2️⃣ DispatchingAndroidInjector 의존성 요청
  : 2번 방법에서 직접 의존성 요청으로 구현하던 DispatchingAndroidInjector입니다. 

3️⃣ super.onCreate()
  : Application 클래스의 onCreate()를 호출해줍니다

4️⃣ DaggerApplication의 applicationInjector() 추상 메서드
  : 사용자가 구현(재정의)할 applicationInjector 추상메서드입니다

5️⃣ HasAndroidInjector 인터페이스의 androidInjector() 추상메서드 구현
  : 2번 방법에서 직접 구현(재정의)했서 DispatchingAndroidInjector를 리턴값으로 넘겼던 추상메서드 입니다.

이렇게 DaggerApplication이 2번방법에서 했던 과정들을 대신 해주게 되므로, 우리는 Application 단위에서 간단하게 사용이 가능합니다

 

DaggerActivity, DaggerFragment 상속/구현 

MainActivity.kt
class MainActivity : DaggerActivity() {	// DaggerActivity() 상속

    @Inject
    lateinit var coffee: Map<Class<*>, @JvmSuppressWildcards Coffee>

    override fun onCreate(savedInstanceState: Bundle?) {

        // AndroidInjection.inject(this@MainActivity)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

먼저 AppCompatActivity 상속을 DaggerActivity 클래스 상속으로 변경합니다

onCreate()를 보면 의존성 주입을 위한 AndroidInjection.inject() 함수를 사용하지않습니다. 그래도 coffee 변수는 @Inject로 의존성을 요청하고 제대로 주입이 됩니다.

Application단위 클래스의 DaggerApplication상속 처럼, DaggerActivity 클래스를 상속함으로 기존에 하던 의존성 주입 과정을 DaggerActivity에게 위임해서 의존성 주입을 합니다.

이제 1,2번 방법처럼 각 Activity / Fragment에서 Dagger 의존성 주입을 위한 메서드체이닝을 구현하지 않아도 알아서 의존성 주입을 하게됩니다

DaggerActivity의 구조는 아래와 같습니다

DaggerActivity.class
/**
 * An {@link Activity} that injects its members in {@link #onCreate(Bundle)} and can be used to
 * inject {@link Fragment}s attached to it.
 */
@Beta
public abstract class DaggerActivity extends Activity implements HasAndroidInjector {	// 1️⃣ 

  @Inject DispatchingAndroidInjector<Object> androidInjector;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    AndroidInjection.inject(this);		// 2️⃣ 
    super.onCreate(savedInstanceState);
  }

  @Override
  public AndroidInjector<Object> androidInjector() {
    return androidInjector;
  }
}

1️⃣ Activity 클래스 상속, HasAndroidInjector 구현
  : Activity 클래스를 상속함으로, MainActivity에서 AppCompatActivity 클래스 대신 DaggerApplication 상속을 가능하게 합니다

2️⃣ AndroidInjection.inject()
  : MainActivity에서 의존성 주입을 위한 AndroidInjection.inject() 메서드를 대신해줘서 의존서 주입을 대신 해줍니다

 

이렇게 Android에서 Dagger2를 사용하기 위한 방법을 공부하면서 포스팅했습니다.

Dagger2가 런닝커브가 높아서, 이해하는데 정말 오래걸렸습니다. 사실 아직도 완벽히 이해하지 않은 상태라서, 내용이 틀릴수 있으니 틀린점이 있으면 알려주시면 감사하겠습니다

 

Dagger2 깃허브 참고하기
 

posth2071/Dagger_Sample_DI

Contribute to posth2071/Dagger_Sample_DI development by creating an account on GitHub.

github.com