The ViewModel class is a business logic or screen level state holder. It exposes state to the UI and encapsulates related business logic. Its principal advantage is that it caches state and persists it through configuration changes. This means that your UI doesn’t have to fetch data again when navigating between activities, or following configuration changes, such as when rotating the screen.
Android 공식 문서에는 ViewModel을 위와 같이 설명하고 있습니다.
ViewModel은 UI 상태를 가지고 있고 관련 비즈니스 로직을 캡슐화 함으로써, Activity 전환 및 configuration change(e.g. 화면 회전) 작업이 발생하더라도 UI가 데이터를 다시 로드하는 과정을 생략할 수 있도록 도와준다고 합니다.
그렇다면 ViewModel은 어떻게 configuration change 작업이 발생하더라도, UI 상태와 같은 값을 유지하고 있을 수 있는걸까요?
이를 알기 위해서는 먼저 ViewModel이 어떻게 생성되고 제거되는지를 알아야 합니다.
The lifecycle of a ViewModel is tied directly to its scope. A ViewModel remains in memory until the ViewModelStoreOwner to which it is scoped disappears.
Android 공식 문서에 따르면, ViewModel의 Lifecycle은 ViewModelScope에 직접적으로 연결되어 있고, ViewModelStoreOwner
가 사라질 때 까지 메모리에 남아있다고 하는데..
사실 이런 말은 직접 와닿지 않기 때문에, 직접 코드를 뜯어보면서 확인해 봅시다.
ViewModel을 생성하는 방법에는 여러가지가 있지만, 본 포스팅에서는 androidx.activity:activity-ktx
에 정의된 방식에 대해서만 다룰 예정입니다.
class MainActivity : AppCompatActivity() {
private val model: MainViewModel by viewModels()
...
}
위의 코드에서 by viewModels()
은 아래와 같이 구현되어 있습니다.
// androidx.activity.ActivityViewModelLazy.kt
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(
VM::class,
{ viewModelStore },
factoryPromise,
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
)
}
여기서 ViewModelLazy
의 생성자로 한 단계 더 들어가면, 아래와 같은 코드를 확인할 수 있습니다.
// androidx.lifecycle.ViewModelLazy.kt
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(
store,
factory,
extrasProducer()
).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
이 코드를 통해 ViewModel 생성은 아래와 같이 이루어짐을 알 수 있습니다.
ViewModelProvider.get()
으로 새로운 ViewModel을 가져옴그렇다면 ViewModelProvider
는 어떻게 ViewModel을 가져올까요?
ViewModelProvider
클래스의 코드를 확인해봅시다.
// androidx.lifecycle.ViewModelProvider.kt
public open class ViewModelProvider
/**
* Creates a ViewModelProvider
*
* @param store `ViewModelStore` where ViewModels will be stored.
* @param factory factory a `Factory` which will be used to instantiate new `ViewModels`
* @param defaultCreationExtras extras to pass to a factory
*/
@JvmOverloads
constructor(
private val store: ViewModelStore,
private val factory: Factory,
private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) {
...
@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이 ViewModelStore
에 있다면 이것을 반환하고, 그렇지 않다면 새로운 ViewModel을 생성 및 반환합니다.
(추가로 이 과정에서 ViewModel을 저장하기 위해, key-value 쌍의 Map
을 사용하는 것을 알 수 있습니다.)
여기서 ViewModelStore
는 ComponentActivity.getViewModelStore()
를 통해 반환된 객체 입니다. (ActivityViewModelLazy.kt
파일의 ViewModelLazy 생성자 호출 부분의 { viewModelStore }
참고)
// androidx.activity.ComponentActivity.java
public class ComponentActivity ... {
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
// Lazily recreated from NonConfigurationInstances by getViewModelStore()
private ViewModelStore mViewModelStore;
...
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
...
}
추가로 ViewModelStore
는 ComponentActivity
의 lifecycle이 처음으로 변경될 때, 생성되는 것을 확인할 수 있습니다.
// androidx.activity.ComponentActivity.java
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
ensureViewModelStore();
getLifecycle().removeObserver(this);
}
});
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
}
여기까지의 내용을 정리해보면,
ViewModelStore
는 Activity가 최초로 onCreate 되었을 때 생성됩니다.ViewModelStore
에 이전에 생성한 ViewModel이 존재한다면, 해당 ViewModel 객체를 반환합니다.ViewModelStore
에 저장합니다.이제 ViewModel의 생성 과정은 대략적으로 알게 되었습니다.
그렇다면 ViewModel은 언제 제거될까요?
ViewModel의 제거 시점은 바로 ViewModelStore.clear()
를 호출할 때 입니다.
ViewModelStore.clear()
가 호출되는 시점을 찾아보면, ComponentActivity
클래스의 생성자에서 호출되는 것을 확인할 수 있습니다.
// androidx.activity.ComponentActivity.java
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
mReportFullyDrawnExecutor.activityDestroyed();
}
}
});
}
코드를 조금 살펴보면,
ViewModelStore.clear()
는 Activity의 lifecycle이 ON_DESTROY
로 변경됨과 동시에 isChangingConfigurations()
값이 false 일 때 호출되는 것을 확인할 수 있습니다.
여기서 isChangingConfigurations()
는 이름으로 충분히 알 수 있듯이 configuration 값이 변경되었는지 여부를 반환하는 함수입니다.
여기까지의 내용을 정리해보면,
ViewModel은 configuration change가 없으면서 Activity가 destroy되면 제거되기 때문에, 화면 전환과 같은 configuration change가 발생하여 Activity가 destroy 되더라도 제거되지 않습니다.
따라서 ViewModel은 configuration change 작업이 발생하더라도, UI 상태와 같은 값을 유지할 수 있음을 알 수 있습니다.
이상으로 ViewModel의 데이터 유지 영업 비밀을 폭로하는 글이었습니다.
잘못된 내용이 있다면 덧글로 지적 부탁드립니다🤗