# Table of Contents
# 안드로이드 의존성 주입 라이브러리들
안드로이드에서 사용하는 대표적인 의존성 주입 라이브러리는 세 가지 입니다.
Koin
Dagger 2
Hilt
Koin
은 코틀린 환경에서 사용할 수 있는 의존성 주입 라이브러리 입니다. 다른 의존성 주입 라이브러리에 비해 상대적으로 배우고 쉽습니다. 그러나 Koin
은 런타임에 리플렉션을 통해 의존성을 주입해주므로 성능이 저하될 수 있습니다.
Dagger
나 Hilt
는 의존성 주입에 어노테이션을 사용합니다. 어노테이션은 컴파일 타임에 코드로 변환되므로 컴파일 타임은 길어질 수 있으나 런타임에 성능이 저하되지 않습니다. 따라서 큰 규모의 프로젝트에서는 Dagger
나 Hilt
가 권장됩니다.
# 설정
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 + 컴포넌트 이름
형태의 클래스가 생성됩니다. 위 AppComponent
는 DaggerAppComponent
클래스를 생성하게 됩니다.
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)
}