# Table of Contents

# 안드로이드 의존성 주입 라이브러리들

안드로이드에서 사용하는 대표적인 의존성 주입 라이브러리는 세 가지 입니다.

  • Koin
  • Dagger 2
  • Hilt

Koin은 코틀린 환경에서 사용할 수 있는 의존성 주입 라이브러리 입니다. 다른 의존성 주입 라이브러리에 비해 상대적으로 배우고 쉽습니다. 그러나 Koin은 런타임에 리플렉션을 통해 의존성을 주입해주므로 성능이 저하될 수 있습니다.

DaggerHilt는 의존성 주입에 어노테이션을 사용합니다. 어노테이션은 컴파일 타임에 코드로 변환되므로 컴파일 타임은 길어질 수 있으나 런타임에 성능이 저하되지 않습니다. 따라서 큰 규모의 프로젝트에서는 DaggerHilt가 권장됩니다.

# 설정

Dagger를 사용하기 위해 의존성을 추가합니다.

apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}

# 사용법

의존성 주입 방법을 표시하기 위해 주입하려는 객체의 생성자 앞에 @Inject를 붙입니다. 아래 예제는 Dagger에게 LoginPresenter 객체를 어떻게 생성하는지를 알려줍니다.

class LoginPresenter @Inject constructor() {
    fun login() {
        // do something.
    }
}

그 다음은 어떤 객체에 의존성을 주입할지 정의합니다. 이 때 컴포넌트를 사용하며, @Component 어노테이션을 추가하면 됩니다. 컴포넌트에는 어떤 객체에 의존성을 주입할지 명시합니다.

@Component
interface AppComponent {
    // LoginActivity에 의존성을 주입한다.
    fun inject(activity: LoginActivity)
}

이제 프로젝트를 리빌드하면 Dagger + 컴포넌트 이름 형태의 클래스가 생성됩니다. 위 AppComponentDaggerAppComponent클래스를 생성하게 됩니다.

Applcation 클래스에서 DaggerAppComponent를 생성하고 초기화합니다.

import android.app.Application
import com.yologger.dagger.di.component.AppComponent
import com.yologger.dagger.di.component.DaggerAppComponent

class App: Application() {

    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        // DaggerAppComponent 생성 및 초기화
        appComponent = DaggerAppComponent.create()
    }
}

이제 LoginActivity에서 의존성을 주입받을 수 있습니다. 우선 (applicationContext as App).appComponent.inject(this)를 호출합니다. 그리고 의존성을 주입받을 변수 앞에 @Inject를 추가합니다.

class LoginActivity : AppCompatActivity() {

    private val buttonLogin: Button by lazy { findViewById(R.id.activity_login_btn_login) }

    // 의존성이 주입될 변수
    @Inject lateinit var loginPresenter: LoginPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        // this(LoginActivity)에 의존성을 주입하겠다고 명시
        (applicationContext as App).appComponent.inject(this)

        buttonLogin.setOnClickListener {
            loginPresenter.login()
        }
    }
}

# 모듈

앞에서 Dagger에게 주입 방법을 알려주기 위해 LoginPresenter의 생성자 앞에 @Inject를 추가했습니다.

class LoginPresenter @Inject constructor() {
    fun login() {
        // do something.
    }
}

만약 @Inject를 사용하지 않고 의존성 주입 방법을 정의하려면 모듈을 사용할 수 있습니다.

// @Inject 어노테이션 제거
class LoginPresenter constructor() {
    fun login() {
        // do something
    }
}

모듈 클래스에는 @Module 어노테이션을 추가합니다.

// PresenterModule.kt

@Module
class PresenterModule {
    // ..
}

그리고 주입할 의존성을 함수에서 반환하는 형태로 정의합니다. 이를 provides 함수라고 하며, @Provides 어노테이션을 추가해야합니다.

// PresenterModule.kt

@Module
class PresenterModule {

    // provide 함수
    @Provides
    fun provideLoginPresenter(): LoginPresenter {
        // LoginPresenter 타입의 변수에 LoginPresenter 객체를 주입한다.
        return LoginPresenter()
    }
}

이제 컴포넌트를 작성합니다. 이 때 @Component 어노테이션의 modules 값에 아래와 같이 모듈을 추가합니다.

// AppComponent.kt
@Component(modules = [PresenterModule::class])
interface AppComponent {

    fun inject(activity: LoginActivity)
}

# 파라미터가 있는 Presenter

만약 아래 예제처럼 생성자에 파라미터가 있는 경우라면 어떻게 해야할까요?

class LoginPresenter constructor(
    // 생성자에 파라미터가 있다.
    private val loginUseCase: LoginUseCase
) {
    fun login() {
        loginUseCase.execute()
    }
}
class LoginUseCase constructor(){
    fun execute() {
        // do something
    }
}
@Component(modules = [PresenterModule::class])
interface AppComponent {
    fun inject(activity: LoginActivity)
}

# @Inject

LoginUseCase@Inject를 붙여주면 됩니다.

class LoginUseCase @Inject constructor(){
    fun execute() {
        // do something
    }
}
@Component(modules = [PresenterModule::class])
interface AppComponent {
    fun inject(activity: LoginActivity)
    fun inject(loginPresenter: LoginPresenter)
}

이제 LoginPresenter를 생성할 때 LogInUseCase의 인스턴스를 전달해야합니다. 따라서 아래와 같이 provides 함수를 수정합니다. Dagger는 함수의 파라미터로 LogInUseCase 객체를 주입해줍니다.

@Module
class PresenterModule {

    // @Provides
    // fun provideLoginPresenter(): LoginPresenter {
    //      return LoginPresenter()
    // }

    @Provides
    fun provideLoginPresenter(loginUseCase: LoginUseCase): LoginPresenter {
        return LoginPresenter(loginUseCase)
    }
}

# @Module

직접 @Module을 정의할 수 있습니다.

// @Inject 어노테이션 제거
class LoginUseCase constructor(){
    fun execute() {
        // do something
    }
}

모듈과 provides 함수를 정의합니다.

@Module
class UseCaseModule {
    @Provides
    fun provideLoginUseCase(): LoginUseCase {
        return LoginUseCase()
    }
}

컴포넌트를 수정합니다.

@Component(modules = [PresenterModule::class, UseCaseModule::class])
interface AppComponent {

    fun inject(activity: LoginActivity)
    fun inject(loginPresenter: LoginPresenter)
}
@Module
class PresenterModule {
    
    @Provides
    fun provideLoginPresenter(loginUseCase: LoginUseCase): LoginPresenter {
        return LoginPresenter(loginUseCase)
    }
}

# 인터페이스에 구현체 주입

UserRepository인터페이스와 구현체인 UserRepositoryImpl가 있습니다.

interface UserRepository {
    fun login()
}
class UserRepositoryImpl: UserRepository {
    override fun login() {
        // do something
    }
}

이제 LoginUseCase의 생성자에 UserRepositoryImpl객체를 주입하려고 합니다.

class LoginUseCase constructor(
    // UserRepository 타입의 파라미터에 UserRepositoryImpl를 주입하려고 합니다.
    private val userRepository: UserRepository
){
    fun execute() {
        userRepository.login()
    }
}

인터페이스에 구현체를 주입하는 방법은 다음과 같습니다.

@Module
class RepositoryModule {

    @Provides
    fun provideUserRepository(): UserRepository {
        return UserRepositoryImpl()
    }
}
@Module
class UseCaseModule {

    // @Provides
    // fun provideLoginUseCase(): LoginUseCase {
    //     return LoginUseCase()
    // }

    @Provides
    fun provideLoginUseCase(userRepository: UserRepository): LoginUseCase {
        return LoginUseCase(userRepository)
    }
}
@Component(modules = [PresenterModule::class, UseCaseModule::class, RepositoryModule::class])
interface AppComponent {

    fun inject(activity: LoginActivity)
    fun inject(loginPresenter: LoginPresenter)
    fun inject(loginUseCase: LoginUseCase)
}