LifeCycle Of ViewModel
❓ViewModel의 생명주기는 왜 중요할까
안드로이드 UI 컴포넌트는 생명주기에 따라 생성되고 파괴된다. 예를 들어 다크모드, 프로세스 강제 종료 후 복원, 화면 회전 같은 configuration change가 일어났을 때 생성→파괴→생성이 반복된다. 따라서 fragment나 activity 내부의 데이터는 모두 초기화된다.
ViewModel은 activity나 fragment의 생명주기보다 한 단계 더 길게 유지된다. 따라서 해당 데이터를 유지하기 위해서는 ViewModel을 사용한다.
🔁 ViewModel의 생명주기
configuration change 대응
왜 ViewModel은 activity나 Fragment에 인스턴스화했음에도 생명주기가 더 길까? 그 이유는 ViewModelStore에 있다. activity가 상속 받는 ComponentActivity는 내부적으로 다음과 같은 구조를 갖는다.
class ComponentActivity : ViewModelStoreOwner {
private var mViewModelStore: ViewModelStore? = null
override fun getViewModelStore(): ViewModelStore {
if (mViewModelStore == null) {
mViewModelStore = ViewModelStore()
}
return mViewModelStore!!
}
}activity 안에 ViewModelStore라는 공간이 따로 존재마고, 모든 ViewModel은 여기에 저장된다.
화면회전 같은 configuration change가 발생하면 안드로이드가 새로운 activity를 만들어주지만, 그 전에 시스템이 이건 단순 재생성이라는 것을 알고 기존 ViewModelStore를 재사용한다. 즉, 기존에 생성되었던 ViewModel의 인스턴스를 그대로 사용한다.
파괴 시점
사용자가 뒤로가기로 나가거나 시스템이 프로세스를 죽이면 이제는 이 activity는 필요 없게 되므로 ViewModel도 같이 파괴해주어야한다. 그 때 안드로이드 프레임워크가 ViewModelStore.clear()를 호출해서 ViewModel의 onCleared()를 실행하고 정리한다.
activity는finish될 때fragment는detach될 때Navigation Entry는back stack에서 제거 될 때
🙋♂️ViewModel 선언(생성)
1. by viewModels()
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels<MainViewModel>()
...
}접근 시점에 ViewModel이 lazy하게 초기화된다. 내부 구현은 아래 코드와 같다.(Activity)
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: { defaultViewModelProviderFactory }
return ViewModelLazy(
VM::class,
{ viewModelStore },
factoryPromise,
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
)
}extrasProducer는viewModel을 생성할 때SavedStateHandle과 같은 추가적인 컨텍스트 정보를 전달한다. 기본적으로는defaultViewModelCreationExtras를 사용한다.factoryProducer는ViewModel의 생성을 어떻게 할지 커스텀할 수 있다. 즉,ViewModelProvider.Factory를 어떻게 얻을지 결정한다.noinline키워드가 붙은 이유는viewModels()는inline함수이므로 호출 시점에 코드가 그대로 복사되어 들어간다. 하지만extrasProducer와factoryProducer는 이 람다를 인라인시키면 런타임에 저장할 수 없기 때문에noinline키워드를 붙여주어야한다.
여기서 this는 ComponentActivity 자신이기 때문에 ViewModelStore를 가지고 있으므로 위에 언급한 내용과 같이 ViewModel의 인스턴스를 저장하고, activity가 파괴되면 ViewModel 또한 파괴시킨다.
2. 직접 생성
class MainActivity : AppCompatActivity() {
private val viewModel by lazy {
ViewModelProvider(this)[MainViewModel::class.java]
}
}1번 코드와 2번 코드의 차이점은 거의 없지만, SavedStateHandle의 자동 주입 차이가 있다.
SavedStateHandle은 프로세스 종료 같은 상황에 ViewModel에서 UI 상태를 안전하게 저장하고 복원할 수 있도록 만들어진 객체이다. 기본적으로 key-value 형태로 데이터를 저장하고, 뷰모델이 생성되었을 때 기존의 저장된 값이 있는 SavedStateHandle를 주입한다.
class MyViewModel(private val state: SavedStateHandle) : ViewModel()다음과 같은 ViewModel이 있다고 가정했을 때, 1번 코드와 같이 위임 방식을 사용하면 viewModels()의defaultViewModelProviderFactory프로퍼티의 SavedStateViewModelFactory가 SavedStateHandle을 생성해서 주입할 수 있도록 해주기 때문에SavedStateHandle의 생성자 주입이 자동으로 된다. 하지만 2번 코드는 직접 수동으로 주입해주어야한다.
🙌 결론
ViewModelStoreOwner의ViewModelStore를 통해 해당 컴포넌트와ViewModel의 생명주기를 맞추거나 configuration Change에 대응할 수 있다.SavedStateHandle을 통해 프로세스가 종료되고viewModel이 파괴되어도 데이터를 복원하여 configuration Change에 대응할 수 있다.