[안드로이드] ViewModel - AAC (Android Architecture Components)
ViewModel
ViewModel 클래스는 비즈니스 로직 또는 화면 상태 홀더이다.
기존의 문제점들과 사용하는 이유
단순한 적은 데이터들은 Activity의 onSavedState() 메소드를 통해 번들 형태로 저장할 수 있다.
하지만 더 큰 데이터들은 해당 방법이 부적합하다.
또 뷰에서 비동기 호출을 하게 되면 액티비티의 생명주기에 따라 리소스가 낭비되는 경우가 있다.
ViewModel의 이점
- UI 상태를 유지할 수 있습니다.
- 비즈니스 로직에 대한 액세스 권한을 제공합니다.
ViewModel의 생명주기
ViewModel의 생명주기는 Activity의 생명주기보다 길다.
ViewModelStoreOwner의 생명주기를 따른다. 그렇기 때문에 액티비티 화면이 회전되어도 ViewModel의 데이터는 유지된다.
*일반적으로 ViewModel은 뷰, Lifecycle, 또는 활동 컨텍스트 참조를 보유할 수 있는 클래스를 참조해서는 안 됩니다. ViewModel 수명 주기가 UI 수명 주기보다 크므로 ViewModel에 수명 주기 관련 API를 보유하면 메모리 누수가 발생할 수 있습니다.
뷰모델의 onCleared()는 Viewmodel이 더 이상 호출되지 않는 순간에 호출된다. (내용 추가하기)
VIewModelStoreOwner, ViewModelStore
interface ViewModelStoreOwner {
/**
* The owned [ViewModelStore]
*/
val viewModelStore: ViewModelStore
}
ViewModelStoreOwner를 통해 viewModelStore를 얻는다.
ViewModelStoreOwner 인터페이스를 구현하는 객체는 ViewModelStore를 관리한다.
어떤 owner(ComponentActivity,Fragment 등)를 통해 생성하느냐에 따라 ViewModel의 생명주기가 결정됨.
open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun put(key: String, viewModel: ViewModel) {
val oldViewModel = map.put(key, viewModel)
oldViewModel?.onCleared()
}
/**
* Returns the `ViewModel` mapped to the given `key` or null if none exists.
*/
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
operator fun get(key: String): ViewModel? {
return map[key]
}
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun keys(): Set<String> {
return HashSet(map.keys)
}
/**
* Clears internal storage and notifies `ViewModel`s that they are no longer used.
*/
fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
}
내부적으로 해쉬맵을 통해 ViewModel을 관리한다.
ViewModelProvider
ViewModel을 인스턴스화할 때 필요한 클래스, ViewModelStoreOwner 인터페이스 구현 객체를 전달한다.
그러면 ViewModel의 생명주기가 VIewModelStoreOwner의 생명주기로 결정된다.
ComponentActivity, Fragment가 ViewModelStoreOwner 인터페이스를 구현한다.
ComponentActivity의 서브 클래스인 AppCompatActivity를 사용하고 있다면 별도로 ViewModelStoreOwner를 구현할 필요는 없다.
@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) }
}
ViewModelProvider를 통해 ViewModel을 얻는다.
아래의 과정과 같다.
- ViewModelProvider를 통해 ViewModel 인스턴스를 요청한다.
- ViewModelProvider 내부에서는 ViewModelStoreOwner를 참조하여 ViewModelStore를 가져온다.
- ViewModelStore에게 이미 생성된(저장된) ViewModel 인스턴스를 요청한다.
- 만약 ViewModelStore가 적합한 ViewModel 인스턴스를 가지고 있지 않다면, Factory를 통해 ViewModel인스턴스를 생성한다.
- 생성한 ViewModel 인스턴스를 ViewModeStore에 저장하고 만들어진 ViewModel 인스턴스를 클라이언트에게 반환한다.
- 똑같은 ViewModel 인스턴스 요청이 들어온다면, 1~3번의 과정을 반복하게 된다.
ViewModelStore가 유지되는 이유는 ViewModelProvider가 getViewModelStore로 store를 얻을 때 기존에 만들어진 store를 재사용한다. 재사용을 하기 위해서 액티비티가 종료되기 전에 일부 변수(ViewModelStore)들을 저장하고 불러오기 때문이다.
ViewModel을 이용한 Fragment 간의 Data 공유
Fragment들과 ViewModel을 공유해 Fragment - Activity 간 결합을 느슨하게 할 수 있다.
Providerder의 매개변수로 공유된 ViewModel은 Activity를 전달하면 해당 ViewModel은 Activity의 생명주기를 따른다.
Reference.
1. https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko
2. https://taetae98.tistory.com/m/10
3. https://charlezz.medium.com/viewmodel이란-무엇인가-viewmodel-초보를-위한-가이드-e1be5dc1ac18