1. AAC ViewModel 이란?
UI 상태를 저장하고 관리할 수 있는 클래스
이점: 상태를 저장하고 Configuration change가 발생해도 유지할 수 있다.
Configuration Change 종류
- 화면 회전
- 언어, 글꼴 변경
- 라이트 & 다크 모드 등
2. Configuration Change가 발생하면 UI 상태가 사라지는 이유
안드로이드에서 Configuration Change가 발생하면 생명주기만 반복되는 것이 아닌 Activity 클래스 자체를 파괴하고 다시 생성한다.
위의 사진이 화면을 회전했을 때의 Activity와 ViewModel의 생명 주기를 나타낸 Log인데 보면 알 수 있듯이 onResume 상태에서 화면 회전이 일어날 경우 onDestroy 이후 다시 Activity를 새로 만들어 UI를 구성하는 것을 볼 수 있다.
따라서 Activity에 저장되어 있던 데이터 및 상태들은 다 날라가고 새로운 Activity가 생기기 때문에 UI 상태가 사라지게 되는 것이다.
3. ViewModel을 사용하면 상태가 유지될 수 있는 이유
이를 알기 위해서는 우선 ViewModel의 LifeCycle에 대해 알아야 한다.
위와 같이 ViewModel의 LifeCycle은 Activity LifeCycle 보다 범위가 넓다.
Activity의 완전히 종료되는 onDestroy()가 호출될 때 onCleared()를 통해 ViewModel이 종료가 된다.
여기서 착각하지 말아야 할 것은 onDestroy()가 되면 onCleared()가 호출되는 것이 아니라는 점이다.
Configuration Change와 같은 재생성의 경우에는 onDestroy()가 발생해도 onCleared()가 호출되지 않는다.
finish()와 같이 완전히 종료되는 순간에만 onCleared()가 호출된다.
이러한 LifeCycle을 갖고 있기 때문에 UI 상태가 유지될 수 있다.
4. ViewModel의 생성 과정
ViewModel은 일반적인 클래스와 달리 ViewModel()을 상속 받고 끝나지 않는다.
ViewModel의 초기화는 위와 같은 방법으로 이루어지는데 과정은 다음과 같다.
1. Activity 또는 Fragment에서 ViewModelProvider에 접근한다.
class MainActivity: T {
// 첫 번째 방법
val viewModel: MainViewModel by lazy {
ViewModelProvider(this)[MainViewModel::class.java]
}
// 두 번째 방법
val viewModel2: MainViewModel by viewModels()
}
- 위에서 Provider에 넘겨주는 this는 MainActivity를 뜻하며 이는 ViewModelStoreOwner로 사용된다.
2. ViewModelProvider에서는 ViewModelStoreOwner로 ViewModelStroe에 접근하여 기존에 생성한 ViewModel이 있는지 확인한다.
// ViewModelProvider.kt
@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
// ViewModelStore에서 ViewModel 확인 후 있으면 반환
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.
}
}
// ViewModelStore에 ViewModel이 없으면 Factory에서 생성
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) } // Store에 저장
}
- 있다면 해당 ViewModel을 반환한다.
- 없다면 Factory에서 생성하여 반환한 뒤 해당 ViewModel을 Store에 저장한다.
ViewModel에 인자 넘기기 (생성자)
공식 문서를 보면 다음과 같은 코드가 있다.
private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }
private val viewModel: MainViewModel by lazy {
ViewModelProvider(this, MyviewModel.Factory)[MainViewModel::class.java]
}
ViewModel을 생성할 때 MyViewModel.Factory와 같은 값을 넘겨준다.
MyViewModel.Factory 의 코드를 살펴보면 다음과 같다.
class MyViewModel(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
return MyViewModel(
(application as MyApplication).myRepository,
savedStateHandle
) as T
}
}
}
}
기본 ViewModelProvider에서 Factory를 통해 생성하는 ViewModel은 인자가 없는 ViewModel이기 때문에
위와 같은 인자를 반환하는 Factory를 따로 생성하여 ViewModelProvider에게 넘겨주면 인자가 있는 ViewModel을 반환해준다.
'안드로이드 > Jetpack' 카테고리의 다른 글
[안드로이드] Navigation Component (0) | 2023.11.29 |
---|---|
[안드로이드] Jetpack (0) | 2023.11.24 |
[안드로이드] LiveData (1) | 2023.11.23 |
[안드로이드] Databinding (1) | 2023.11.23 |
[안드로이드] Room DB (0) | 2023.11.23 |