
개발을 진행하면서 MVVM 패턴의 개선점을 아래와 같이 느끼게 되었다.
1. 성격이 다른 2개이상의 api 응답 값을 통해 하나의 UI로 표출을 하고자 할때, 중복코드가 많이 생성된다.
2. 개발중인 상황에서 api의 응답값이 자주 변경이 이루어지는 경우, 참조하는 변수를 수정해야 한다.
3. View와 ViewModel 관계가 1:1로 개발을 진행되었을 경우, 중복코드가 많이 생성된다.
youtube를 예를 들면
하나의 리스트 안에 shorts동영상과, 일반 동영상이 같이 노출되는것을 볼수 있다.

ViewModel에서 Normal Video 응답값과 Shorts Video 응답값을 Modeli을 구성해야한다.
Modeling을 하는 과정 중에, 아래와 같은 고민이 생긴다.
ViewModel은 재 가공된 Model에만 관심이 있어야한다.
서로 다른 api의 응답값에 대해서 Model구성하는 코드는 ViewModel에 있어서는 안된다고 생각한다.
그래서 ViewModel과 Repositroy 사이에 Domain Layer(선택)를 두려고 한다.
참조 : Android Developer Domain Layer

ViewModel에서 Model 구성하는 부분들을 Domain Layer(Usecase)로 위임한다.
ViewModel은 Model 구성에 대한 관심이 사라지게되며, ViewModel Model 구성하는 중복코드들이 사라지게된다.
리뉴얼 혹은 신규 개발시 Ui 선작업을 하는 경우가 많은데 api 응답값이 변경이 자주 일어났을때 해당되는 케이스이다.
보통 서버에서 받아온 api 응답값이 View까지 반환된다.
응답값이 View 바인딩이 된 상태라면, Response이 변경이 일어나면 참조되고 있는 변수를 수정을 해야되는 일들이 발생한다.
위에서 언급된 Domain Layer의 내용을 추가로 적는다.
Domain Layer에서 ViewModel에 친화적인 Modeling을 하게되면,
View / ViewModel에서 참조하는 데이터는 ViewModel에 친화적인 Model이기 때문에
api 응답값이 변경되더라도 Modeling한 데이터에서 추가 수정이 이루어진다.
예를 들어 물건을 구매하는 앱들을 살펴보면, 예약자명 / 휴대폰번호가 자동으로 입력되어지는 경우가 많다.
nav_main.xml / MemberViewModel.kt
fun reqGetMemberInfo(){
when (val result = memberRepository.reqGetMemberInfo()) {
// 회원정보를 표기하기 위한 프레젠테이션 로직
}
}
nav_purchase.xml / PurchaseRequestViewModel.kt
fun reqGetMemberInfo(){
when (val result = memberRepository.reqGetMemberInfo()) {
// 구매를 하기 위한 회원정보 표기 프레젠테이션 로직
}
}
위와 같이 목적은 다르지만, 중복(비슷한) 코드가 많다.
앞서 설명한 ViewModelScope 개념을 이용한다.
ViewModelProvider.kt
@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel!!)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
DetailContnetFragment.kt
val recentViewModel = ViewModelProvider(findNavController().getViewModelStoreOwner(R.id.nav_main))[RecentViewModel::class.java]
ViewModelProvider를 통해 RecentViewModel.kt을 가지고 오려고 할때, ViewModelStore에 존재하면 RecentViewModel.kt 반환
존재하지않으면 생성해서 반환 해준다.
RecentViewModel.kt는 nav_main.xml의 ViewModelScope를 따르기 떄문에
nav_main.xml 존재하는한 RecentViewModel을 가지고 올수있다.
위와 같이 멤버 정보를 가지고 오는 api를 PurchaseRequestViewModel.kt 요청해서 가지고 오는게 아니고,
MemberViewModel.kt 접근해서 이미 받아온 멤버 데이터면 해당 데이터를 반환하고
없는 경우에는 api 통해서 받아오면
중복코드도 줄고, 데이터를 한 곳에서 관리 할 수 있다는 장점이 있다.