본문 바로가기

Android/꼭 공부해야 할 라이브러리

Koin을 통해 의존성 주입하기

0. 의존성 주입에 대해서

1) 의존성(Dependency)이란?

현재 객체가 다른 객체와 상호작용을 하고 있다면 현재 객체는 다른 객체에 의존성을 가진다고 합니다.

의존성이 높은 경우, 하나의 모듈이 바뀌면 의존하고 있던 다른 모듈까지 변경되어야 합니다.

또한 두 객체 사이에 의존성이 존재한다면 단위 테스트를 하기 어려워집니다.

따라서 이를 해결하기 위해 의존성 주입 개념을 도입할 수 있습니다.

2) 의존성 주입(Dependency Injection)이란?

내부에서 객체를 생성하는 것이 아니라, 외부에서 객체를 생성하여 의존성을 주입해주는 것입니다.

a. 장점

코드의 재사용성을 향상시키고, 더 간결하게 코드를 작성할 수 있습니다.

또한 객체의 생성과 사용을 분리시켜 종속된 코드를 줄여줍니다.

이를 통해 코드의 유연성과 확장성이 증진됩니다.

b. 사용 방법

Dagger, Koin, Hilt 등 의존성 주입을 위한 많은 라이브러리들이 있습니다. 저는 그 중에서 오늘 Koin에 대해서 다루어보겠습니다.

1. Koin

의존성 주입 라이브러리 중 하나입니다.

순수 코틀린으로만 제작되었으며, 경량화가 되어있고, 러닝커브가 낮습니다.

1-1. DSL(Domain Specific Language)

Koin은 DSL(도메인 특화 언어)입니다. 쉽게 말하면, 하나의 도메인당 하나의 역할이 주어져있으며, 사용하려는 목적에 맞는 도메인을 사용하는 언어입니다.

DSL 키워드는 여러 개가 있으며, 자세한 사항은 아래 주소를 참조하는 것이 좋습니다.

https://insert-koin.io/docs/reference/koin-core/dsl

 

Koin DSL | Koin

Thanks to the power of Kotlin language, Koin provides a DSL to help your describe your app instead of annotate it or generate code for it. With its Kotlin DSL, Koin offers a smart functional API to achieve to prepare your dependency injection.

insert-koin.io

2. 만드는 방법

0) 준비

build.gradle(:app) 파일의 dependencies에 아래를 추가합니다.

def koin_version = '3.1.3'

implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"

1) KoinApplication 생성

이때 이 구문은 앱의 Application에서 선언해주어야 합니다.

startKoin {
            androidLogger(Level.NONE)
            androidContext(this@ApplicationClass)
            modules(appModule, mViewModel)
        }

 

 

a. androidLogger

AndroidLogger 를 Koin Logger로 사용합니다.

이때 Level.NONE 이나 Level.ERROR를 설정하지 않으면 예외가 발생할 수 있으므로, 이를 주의해야 합니다.

b. andriodContext

해당 안드로이드의 context를 사용하도록 설정합니다.

c. modules

사용할 모듈을 여기에 등록합니다. 쉼표(,)로 구분하여 추가하면 됩니다.

2) 모듈 생성

val appModule = module{
single {BeerSpecificRepository(BeerSpecificService.getInstance())}
}
val mViewModel = module{
viewModel{BeerSpecificViewModel(get())}
}

위의 appModule은 일반 모듈 생성에 대한 예시이고, 그 아래는 viewModel을 생성할 때 사용되는 모듈입니다.

위에 사용될 수 있는 도메인 키워드에는 다음과 같은 것들이 있습니다.

a. factory

요청 시마다 새로운 인스턴스를 만듧니다.

b. single

단일 인스턴스를 반환합니다.

기본적으로 모든 객체 생성은 요청할 때 이루어집니다. 하지만 모듈 선언과 동시에 생성이 필요한 경우엔 createdAtStart 속성을 true로 설정해주면 됩니다.

동일 타입의 객체는 한 번만 선언할 수 있습니다. 여러 개를 선언하면 모듈 로드 과정에서 예외가 발생합니다. 만약 동일 타입의 객체가 여럿 필요하다면, qualifier를 지정하면 됩니다.

c. scopted

scope 내에서 단일 인스턴스를 반환합니다.

scope에는 이름을 한정한 closed scope와 이름을 한정하지 않은 open scope로 나뉘어집니다.

scope 인스턴스는 id와 name을 가지는데, name은 scope의 유형, id는 scope의 고유 식별자로 보면 됩니다.

open scope 로 선언한 객체는 어느 scope에서나 가져다 쓸 수 있지만, closed scope로 선언한 객체는 해당 name을 가진 scope에서만 가져다 쓸 수 있습니다.

d. get()

컴포넌트 내에서 알맞은 의존성을 주입받습니다.

위에서 BeerSpecificViewModel 은 BeerSpecificRepository를 파라미터로 전달받아야 하는데, 이때 위의 모듈에서 해당 타입으로 선언된 것이 있으므로 Koin이 알아서 이를 주입해줍니다.

get()은 완전 짱짱입니다.

e. viewmodel()

MVVM의 ViewModel 전용으로 사용되는 DLS 키워드입니다.

3) 의존성 주입

private lateinit var viewModel : BeerSpecificViewModel
private val beerSpecificService = BeerSpecificService.getInstance()

viewModel = ViewModelProvider(this, BeerSpecificViewModelFactory(BeerSpecificRepository(beerSpecificService)))[BeerSpecificViewModel::class.java]

원래 이런 식으로 작성해야 하는 코드를 의존성 주입으로 바꾼다면

private val mViewModel : BeerSpecificViewModel by viewModel()

이렇게 바뀝니다. 정말 코드가 간결해지고 깔끔해진 걸 확인할 수 있습니다.

3. fragment 와 activity 간 뷰모델 공유할 경우

기존에 fragment 에서는 다음과 같이 뷰모델을 사용했었습니다.

private lateinit var mViewModel : BeerSpecificViewModel
mViewModel = ViewModelProvider(requireActivity())[BeerSpecificViewModel::class.java]

그러나 Koin을 사용하면 sharedViewModel을 통해 간략하게 작성할 수 있습니다.

private val mViewModel : BeerSpecificViewModel by sharedViewModel()

4. 마무리

개념이 다소 어려울 수 있지만, Koin 뿐만 아니라 의존성 주입 방법을 사용한다면 코드를 간략하게 작성할 수 있고 객체 끼리의 의존성을 낮춰서 단위 테스트나 모듈 간 변경에 유리합니다.

따라서 이번 시간에 잘 정리되었으면 좋겠습니다.

728x90
반응형