2020. 4. 24. 14:50ㆍDI/Koin
<Koin 이란 ?>
- Kotlin 개발자를위한 실용적인 경량 DI(dependency injection: 의존성 주입) 프레임 워크
- 프록시, 코드 생성, 리플렉션이없는 기능적 해상도 만 사용하여 순수한 Kotlin으로 작성된 DI 라이브러리
"Koin is a DSL, a light container and a pragmatic API"
(여기서 DSL이란 ? Domain-Specific Languages : 도메인 특화 언어 즉, 특정 분야에 최적화된 프로그래밍 언어)
* Dagger2 보다 진입장벽이 낮고 kotlin에 특화되어있어 Kotlin 사용자가 사용하기 더욱더 편리하다
<Koin 사용 설정 하기>
1. build.gradle(:project)
buildscript {
ext{
koin_version = '1.0.2'
}
//Add Jcenter to your repositories if needed
repositories {
jcenter()
}
}
2. build.gradle(:app)
dependencies {
// Koin AndroidX Scope feature
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel feature
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Fragment Factory (unstable version)
implementation "org.koin:koin-androidx-fragment:$koin_version"
}
<Koin 시작하기>
Koin은 DSL, 컨테이너 및 pragamtic API로 종속성을 활용합니다.
Koin DSL 은 다음과 같이 구성됨
- KoinApplication DSL: 제공된 모듈 명세를 이용해 객체 인스턴스를 관리
- Module DSL: 제공할 객체의 명세
1. KoinApplicaion 생성
Koin DSL 키워드에 대한 간단한 요약 :
- startKoin { } -KoinApplication 인스턴스에 따라 생성 및 등록
- koinApplication { } -KoinApplication 인스턴스 생성
- modules(...) -사용한 모듈 선언
- logger() -PrintLogger 선언
- properties(...) -지도 속성 선언
- fileProperties() -외부 파일의 속성 사용
- environmentProperties() -환경 속성 사용
- androidLogger() -Android Koin 로거 선언
- androidContext(...) -주어진 안드로이드 컨텍스트를 사용
- androidFileProperties() -안드로이드 자산의 속성 파일 사용
- slf4jLogger(...) -SLF4J 로거 사용
a. startKoin 사용
- AndroidManifest.xml
<application
android:name=".MyApplication"
...
</application>
- MyApplicaion.kt
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
// startKoin 호출
startKoin(
androidContext = applicationContext,
modules = myDiModule
)
}
}
b. Custom Koin instance 생성
// Create and register following KoinApplication instance
startKoin {
logger()
modules(coffeeAppModule)
}
// create KoinApplication instance
koinApplication {
logger()
modules(coffeeAppModule)
}
2. module 생성 (myModule.kt)
Koin DSL 키워드에 대한 간단한 요약 :
- module { } -Koin 모듈 또는 하위 모듈 만들기 (모듈 내부)
- factory { }- 팩토리 빈 정의를 제공
- single { } -빈 정의를 제공
- get() -컴포넌트 의존성 해결
- named() -타입, 열거 형 또는 문자열로 한정자를 정의
- bind -지정된 bean 정의에 대한 추가 Kotlin 유형 바인딩
- binds -주어진 빈 정의에 대한 추가 Kotlin 유형 목록
- getProperty() -코인 부동산을 해결
Koin 모듈은 모든 구성 요소를 선언 할 수 있는 공간 입니다. 이 module함수를 사용하여 Koin 모듈을 선언.
val myModule = module {
// your dependencies here
}
mvvm 패턴 기준으로 먼저, 아래와 같이 클래스를 생성한다 .
1. "retrofit".class -> retrofitPart = module 에 추가
2. "model".class -> modelPart = module 에 추가
3. "viewModel".class -> viewModelPart = module 에 추가
- myModule.kt
val retrofitPart = module {
single<RetrofitService> {
RetrofitFactory.build().create(RetrofitService::class.java)
}
}
var adapterPart = module {
factory {
CustomRecyclerViewAdapter()
}
}
var modelPart = module {
factory<DataModel> {
DataModelImpl(get())
}
}
var viewModelPart = module {
viewModel {
MainViewModel(get())
}
}
//startKoin에서 나열할 module
var myDiModule = listOf(retrofitPart, adapterPart, modelPart, viewModelPart)
정의한 모듈을 startKoin에서 나열해야하는데 미리 myDiModule로 묶어두면 myDiModule만 호출하면 됩니다.
모듈에 쓰인 클래스들
1. RetrofitService.kt
interface RetrofitService {
@GET("/v2/search/image")
fun searchImage(@Header("Authorization") auth:String,
@Query("query") query:String,
@Query("sort") sort:String,
@Query("page") page:Int,
@Query("size") size:Int): Single<ImageSearchResponse>
}
2. CustomRecyclerViewAdapter.kt
class MainSearchRecyclerViewAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
...
}
3. DataModel.kt
interface DataModel {
fun getData(query:String, sort: String, page:Int, size:Int): Single<ImageSearchResponse>
}
4. DataModelImpl.kt
class DataModelImpl(private val service: RetrofitService): DataModel{
private val API_KEY = "a4a7dc0b83f626751bdba54aa76936d5"
override fun getData(query:String, sort: String, page:Int, size:Int): Single<ImageSearchResponse> {
return service.searchImage(auth = API_KEY, query = query, sort = sort.sort, page = page, size = size)
}
}
5. MainViewModel.kt
class MainViewModel(private val model: DataModel): BaseKtViewModel(){
companion object{
private val TAG = MainViewModel::class.java.simpleName
}
private val _imageSearchResponseLiveData = MutableLiveData<ImageSearchResponse>()
val imageSearchResponseLiveData: LiveData<ImageSearchResponse>
get() = _imageSearchResponseLiveData
fun getImageSearch(query: String, page: Int, size: Int){
addDisposable(model.getData(query, "recency", page, size)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
it.run {
if(documents.size > 0) {
Log.d(TAG, "document: $documents")
_imageSearchResponseLiveData.postValue(this)
}
Log.d(TAG, "meta : $meta")
}
},{
Log.d(TAG, "response error, message : ${it.message}")
}))
}
}
3. 실제 사용 방법 (주입받는 방법)
- get() 또는 by inject() 를 통해 바로 의존성 주입
- getKoin() 현재 Koin인스턴스 에 액세스
Koin은 ViewModel을 관리하기위한 특수 기능을 제공합니다.
- viewModel ViewModel을 선언하는 특수 DSL 키워드
- by viewModel() & getViewModel() 에서 ViewModel 인스턴스를 주입합니다 ( Activity&에서 Fragment)
- by sharedViewModel() & getSharedViewModel() ( Fragment) 에서 호스팅 활동에서 ViewModel 인스턴스를 재사용
- MainActivity.class
class MainActivity :
BaseKtActivity<ActivityMainBinding, MainViewModel>() {
...
override val viewModel: MainViewModel by viewModel()
private val mainSearchRecyclerViewAdapter: MainSearchRecyclerViewAdapter by inject()
...
companion object {
private val TAG = MainActivity::class.java.simpleName
}
}
* 더 자세한 내용은 Koin 공식 사이트, Koin 문서, Koin github 를 참고하세요 !!