- 서비스의 기존 로직에 UI 로직과 DB 접근, 네트워크 로직을 복잡하게 모두 차지하고 있는 Manager 클래스가 있었다.
- 작업 시에 혼동이 오고 유지보수다 어렵다 보니 에러 발생 시 찾기가 쉽지 않았고, 스레드에 대한 문제도 빈번하게 발생하고 있었다.
- 어느 정도 간단한 DB 접근은 문제없다 해도 데이터가 많다면 ANR이 발생.. 이를 위해 비동기 처리가 누락된 부분을 탐색했지만 매니저 클래스를 나눠버리는 게 나을 것으로 확인되었다.
- 아예 MVVM 패턴을 적용하도록 했다. 내부의 DB 쿼리 작업들이 명시된 ~DAO 클래스들은 내부적으로 AppContext를 사용하고 있는데, 이 의존성을 전부 해결하려면 상당한 시간이 걸려.. 일단은 당장 ANR 문제를 해결하기 위해 Hilt 없이 MVVM 패턴을 구성했다.
- 여기서 MVVM 패턴에 대해 잠시 다시 확인해보고 넘어가자

- Model의 데이터를 가공하는 ViewModel과, 그 ViewModel을 보여주는 View로 이루어진 패턴
- 그리고 Observer나 LiveData로 데이터의 변화를 감지할 수 있도록 하는데, 세션에서 한 번만 실행하는 단일 쿼리의 경우에는 굳이 그럴 필요가 없다고 생각한다. 내 예제에서는 그냥 비동기 처리만을 위해 사용되었지만.. 추후 해당 ViewModel이 타깃으로 한 데이터를 어떻게 가져올 수 있을지는 모르기 때문에 가공 및 확장 가능한 ViewModel을 아예 구성해 두었다.
class MainDataViewModel : ViewModel() {
private val repo: Repo() = Repo()
fun upsert(data: Data) {
viewModelScope.launch(Dispatchers.IO) {
repo.updateOrInsert(data)
}
}
fun getDatas(
~~~
): LiveData<List<Data>> =
repo.getDatas(
~~~
).asLiveData(Dispatchers.IO)
fun loadDDayDatas(onFetched: (List<Data>) -> Unit) {
viewModelScope.launch {
val datas = withContext(Dispatchers.IO) {
repo.getDDayDatas()
}
onFetched.invoke(datas)
}
}
}
- upsert 함수는 IO 디스패처 레벨에서 데이터의 수정 및 추가를 반영시키도록 한다.
- 여기서 유의할 점은 ViewModel은 현재 Activity 수명과 함께하기 때문에 저장 작업이 혹시 지연된다면 화면 종료 시 데이터 저장에 실패할 수 있다. 보통 저장 이후 -> 화면 종료하던지, 인디케이터가 표시되던지 하지만 아예 라이프 사이클 영향 없이 처리하려면 object Manager로 비동기 처리하는 게 나을 수 있다는 생각이다.
- getDatas의 경우에는 Repository에서 Flow <List <Data>> 형태로 방출하는 데이터를 LiveData로 IO 디스패처 레벨에서 받아온다. 그리고 LiveData를 통해 UI에서 Observe가 가능하기에 데이터 동기화 시, Data 목록을 변경 사항에 따라 자동으로 갱신 처리 등이 가능하다.
- DDayBlocks의 경우에는 IO 디스패처에서 데이터를 쿼리 해서 가져오는 단순 작업을 진행한다.
- Hilt를 사용하지 않기 때문에 ViewModel을 사용하려면 ViewModelFactory를 구성해주어야 한다. 사실 이런 보일러 플레이트를 구성할 필요가 없는 게 DI 라이브러리의 장점이라고 본다.
class MainDataViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainDataViewModel::class.java)) return MainDataViewModel() as T
throw IllegalArgumentException("Unknown ViewModel class")
}
}
- ViewModelFactory는 생성자 매개 변수 없이 ViewModel 자체를 인스턴스화한다.
- isAssignableFrom으로 요청 클래스가 TimeBlockViewModel인지 확인
- 맞으면 TimeBlockViewModel() 직접 생성해 반환
- ViewModelProvider는 해당 owner(Activity/Fragment)의 ViewModelStore에서 캐시 조회하고 타깃 ViewModel이 존재하지 않는다면 Factory를 통해 새롭게 생성해 준다.
val factory = MainDataViewModelFactory()
mainDataViewModel = ViewModelProvider(this, factory)[MainDataViewModel::class.java]
- Manager에 뭉쳐있던 코드들을 정리하고, 비동기 처리 대응도 추가될 수 있었다.