자, 이전 포스팅에서 작성한 Koin으로 리팩토링 해보자.
Koin에 대해선 위 사이트를 방문해보는 것을 추천한다.
간략하게 말하면, Kotlin으로만 작성된 DI Framework이다. Dagger에 비해 상대적으로 쉽게 작성이 가능하다.
단점으로는 런타임, 실제 앱이 실행되는 동안에 DI에 문제가 발생했는지 안했는지 알 수 있다.
(Dagger는 컴파일 타임에 알 수 있다.)
자, 이제 Koin을 사용해보자.
- Dependency 추가
위 깃허브 사이트의 메인에서 하란대로 추가하자.
이 글 작성 시점 최신 버전인 '2.2.0-rc-3' 버전으로 하겠다.
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
}
dependencies {
...
classpath "org.koin:koin-gradle-plugin:2.2.0-rc-3"
}
}
allprojects {
...
}
...
우선 프로젝트 레벨 그래들의 dependencies에 koin 플러그인을 추가한다.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
...
}
dependencies {
...
// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:2.2.0-rc-3"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:2.2.0-rc-3"
}
그리고 앱 레벨 그래들의 dependencies에 koin을 추가한다.
- 리팩토링
github.com/DuItDDu/Android-Codelabs/tree/master/Android-Dependency-Injection
결과를 먼저 보고싶다면, 위 깃허브에서 koin 브랜치를 체크아웃하면 된다.
변경된 부분은 koin 패키지와 MyApplication이 추가 되었다.
그리고 MainActivity, MainViewModel이 변경되었다.
순서는 koin을 위한 모듈 생성(koin 패키지) -> MyApplication 생성 후 koin적용 -> MainActivity, MainViewModel 수정 순으로 하였다.
- Koin 모듈
// ViewModelModule.kt
val viewModelModule = module {
viewModel(qualifier = named("Remote")) {
MainViewModel(get(qualifier = named("Remote")))
}
viewModel(qualifier = named("Local")) {
MainViewModel(get(qualifier = named("Local")))
}
}
// RepositoryModule.kt
val repositoryModule = module {
single(qualifier = named("Remote")) {
DataRepository(get(qualifier = named("Remote")) as RemoteDataSourceImpl)
}
single(qualifier = named("Local")) {
DataRepository(get(qualifier = named("Local")) as LocalDataSourceImpl)
}
}
// DataSourceModule.kt
val dataSourceModule = module {
single(qualifier = named("Remote")) {
RemoteDataSourceImpl()
}
single(qualifier = named("Local")) {
LocalDataSourceImpl()
}
}
모듈은 위와 같다. 각각의 모듈은 Remote, Local의 Scope name을 갖는다. 실제 현업이라면 상수 혹은 Enum으로 정의하여 활용할 수 있겠다.
ViewModel 생성 시 Scope name에 따라 Remote, Local을 분기한 것이다.
- MyApplication
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
modules(
listOf(
viewModelModule,
repositoryModule,
dataSourceModule
)
)
}
}
}
별것 없다. 앞서 생성한 모듈을을 추가하여 startKoin 에 전달한다.
* 메니페스트 파일에 아래와 같이 MyApplication 을 추가하는 것을 잊지않아야 한다.
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
</application>
- MainActivity, MainViewMode 리팩토링
class MainActivity : AppCompatActivity() {
private var isRemote: Boolean = false
private lateinit var dataListAdapter: DataListAdapter
private val viewModel: MainViewModel by viewModel(qualifier = named(if (isRemote) {
"Remote"
} else {
"Local"
}))
override fun onCreate(savedInstanceState: Bundle?) {
...
}
private fun initView() {
...
}
private fun initViewModel() {
viewModel.dataList.observe(this) {
dataListAdapter.dataList = it
}
viewModel.loadDataList()
}
}
class MainViewModel(private val repository: DataRepository) : ViewModel() {
private val _dataList: MutableLiveData<List<Data>> = MutableLiveData()
val dataList: LiveData<List<Data>> = _dataList
fun loadDataList() {
_dataList.postValue(repository.loadDataList() ?: emptyList())
}
fun saveData(data: Data) {
repository.saveData(data).let {
if(it) {
loadDataList()
}
}
}
}
MainActivity는 위와 같이 수정되었다.
기반코드의 initViewModel 메서드에서 했던 행위가 상단의 by viewModel ... 로 수정되었다.
isRemote의 값에 따라 Scope name만 변경되어 전달되고 하는것이 딱히 없다.
이 예시와 달리 isRemote 같은 것이 필요없다면, by viewModel() 로 완료된다.
MainViewModel에선 Factory가 삭제되었다. Koin이 이를 대신해주기 때문이다.
자, 이제 ViewModel이 굉장히 많고 다양한 모듈을 가지고, 생성자가 복잡한데 이곳저곳에서 사용되는 모듈이 있다고 상상해보자.
Koin을 사용하지 않으면 Factory만들고, ViewModel을 생성할 때 마다 생성자에 주입할 복잡한 생성자를 갖는 모듈들을 만들어야 한다.
하지만 Koin을 사용하면 Module에서 한 번만 생성하고 언제든지 편하게 가져다 쓸 수 있고, 상황에 따라 객체를 변경할 수도 있다.
이제 DI에 대한 개념과 편리함을 이해했기를 바란다.
다음 포스팅에선 Hilt를 이용하여 동일하게 구현해 볼 것이다.
'개발 > Android' 카테고리의 다른 글
[Android] Android Compose + MVVM 맛보기! (2) | 2020.11.18 |
---|---|
[Android] Dependency Injection (a.k.a DI) - 4. 기반코드를 Hilt로 리팩토링 해보자! (0) | 2020.10.25 |
[Android] Dependency Injection (a.k.a DI) - 2. MVVM으로 기반 코드 작성 (0) | 2020.10.24 |
[Android] Dependency Injection (a.k.a DI) - 1. 뭔데? 왜 하는데? Dagger? Koin? Hilt? (0) | 2020.10.24 |
[Android] Kotlin + BottomNavigationView + Fragment + ViewPager2 사용하기 (0) | 2020.08.06 |