Android Dagger 사용방법 3가지
Dagger 관련 글
- DI (Dependency Injection) - 의존성 주입
- Dagger2 #1 - 기본 개념
- Dagger2 #2 - Scope / Binds / MultiBinding
- Dagger2 #3 - @Named @Qualifier
- Dagger2 #4 - context 주입방법 @BindsInstance @Component.Builder @Component.Factory
- Dagger2 #5 - Android Dagger 사용방법 3가지 (현재 글)
: HasAndroidInjector, DaggerApplication, DispatchingAndroidInjector - Dagger2 #6 - Retrofit + DaggerApplication Factory 예시
먼저 2번, 3번에서 사용되는 AndroidInjector를 사용하기 위해 Gradle 설정을 합니다
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가지
- Component Module Scope를 모두 직접 기술해서 사용하는 방법
- DispatchingAdnroidInjector를 제공하는 방법
: AndroidInjectionModule, HasAndroidInjector, DispatchingAndroidInjector - 안드로이드 기반클래스(DaggerApplication, DaggerActivity, DaggerFragment 등)로 제공하는 방법
: AndroidSupportInjectionModule, DaggerApplication, DaggerActivity, DaggerFragment
Component Module 직접 사용
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를 사용하기 위해 먼저 추가적으로 필요한 설정이 존재합니다
- AndroidInjectionModule 추가 (ApplicationComponent의 modules)
- ActivityBindingModule 추가 (ApplicationComponent의 modules)
- AndroidInjector<Activity 또는 Fragment> 상속 (SubComponent interface에 상속)
- AndroidInjector.Factory<Activity 또는 Fragment) 상속 (SubComponent.Factory interface에 상속)
- Application 단위 클래스에서 HasAndroidInjector를 상속하고 androidInjector() 메서드를 구현하기(DispatchingAndroidInjector를 반환)
- 각 Activity / Fragment에서 AndroidInjection.inject()로 의존성 주입을 완료기
조건을 하나씩 설정하겠습니다
AndroidInjectionModule 추가
ActivityBindingModule 추가
@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) 상속
@Subcomponent(modules=[...])
interface MainComponent : AndroidInjector<MainActivity> {
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<MainActivity> {}
}
MainActivity에서 사용할 SubComponent인 MainComponent를 AndroidInjector<MainActivity>로 상속해서 구현했습니다.
이제 AppComponent와 부모-자식 관계를 설정하기 위해 ActivityBindingModule에 MultiBinding 설정하겠습니다
@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 어노테이션을 사용한 추상메서드로 대체할 수 있습니다
@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를 반환)
// 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()로 의존성 주입
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 인터페이스 상속
@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 상속/구현
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 구조는 아래와 같습니다
@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 상속/구현
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의 구조는 아래와 같습니다
/**
* 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 깃허브 참고하기