[번역] ViewModel in Android: Deep Dive

이현우·2021년 12월 13일
1

Deep Dive: Android

목록 보기
10/10
post-thumbnail

당신이 안드로이드 개발자라면, MVVM이라는 단어를 일할 때나 아니면 인터뷰에서라도 들어본 적이 있을 것이다.

Model-View-ViewModel(MVVM)은 MVP와 MVC 아키텍처의 결점들을 해소한 아키텍처로 알려져있다. MVVM에서는 Presentation 로직과 비즈니스 로직을 분리시키는 것을 설계 시 권장하고 있다.


ViewModel

MVVM은 앱의 Configuration Change가 일어나도 상태를 보존시킬 수 있는 디자인 패턴으로 유명하다(필자 주: 사실은 이는 MVVM 아키텍처가 아닌 Android에서 제공하는 AAC-ViewModel(이하, ViewModel)만의 Feature이다). 우선 구현된 코드를 먼저 살펴보면서 ViewModel이 Activity/Fragment가 Destory 되어도 어떻게 상태를 보존시킬 수 있게 하는 지 알아보자.

  • 우선 ViewModel 클래스를 상속받는 서브클래스를 만들어보자. 서브클래스 안에는 카운터 멤버변수를 추가할 것이다.
class MainActivityViewModel : ViewModel() {
  private val _counterLiveData = MutableLiveData(0)
  val counterLiveData: LiveData<Int> = _counterLiveData

  fun incrementByOne() {
    _counterLiveData.value = _counterLiveData.value?.plus(1)
  }
}
  • Fragment에 ViewModel 객체를 선언해보자. ViewModel 객체를 처음 만드는 사람들이라면 일반 클래스에서 객체를 만드는 것처럼 생성자를 활용하여 객체를 만들 것이다. 아래와 같이
val viewModel = MainActivityViewModel()
findViewById<Button>(R.id.btn_increment).setOnClickListener {
  viewModel.incrementByOne()
}
viewModel.counterLiveData.observe(this) { counter ->
  findViewById<TextView>(R.id.tv_counter).text = "$counter"
}

위와 같이 만들게 된다면 에러는 발생하지는 않지만 Configuration Change가 일어나도 상태가 보존되지 않는다. 이는 우리가 알고 있는 ViewModel의 장점이 아니다, 그렇다면 우리가 알고 있는 ViewModel을 만들기 위해서는 어떻게 ViewModel 객체를 만들어야할까? 바로 ViewModelProvider 객체를 통해 ViewModel 객체를 생성시키면 된다.


val viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
findViewById<Button>(R.id.btn_increment).setOnClickListener {
  viewModel.incrementByOne()
}
viewModel.counterLiveData.observe(this) { counter ->
  findViewById<TextView>(R.id.tv_counter).text = "$counter"
}

기기를 가로/세로로 회전시켰을 때 카운터 변수의 값은 그대로 보존될 것이다. 그러나 이걸 보면서 다음과 같은 생각이 들 수 있다.

아니 도대체 왜 ViewModel 객체는 직접 생성자로 만들지 않고 저런 방식으로 귀찮게 만들어야하지?
그리고 ViewModelProvider는 뭐길래 상태보존할 수 있는 ViewModel을 만들어내지?

궁금하다면 아래를 조금 더 읽어내려가보자

ViewModelProvider

Configuration Change가 일어날 때의 Acticity 생명주기

앱의 Configuration Change가 일어날 때 Activity는 어떤 일이 일어나길래 상태 보존을 못할까?

Configuration Change가 발생할 때 안드로이드는 현재 Activity를 파괴(Destory)하고(즉 onPause(), onStop(), onDestroy()를 거치고) 그 Activity를 재시작(onCreate(), onStart(), onResume()을 호출한다)한다.

이때, 기존 Activity에서 만들어진 객체들이나 저장된 상태들을 파괴될 때 다 버려지므로 상태보존이 안되는 것이다.

ViewModelProvider는 Configuration Change가 일어나도 안 파괴되나?

그렇다면 자연스럽게 나올 수 있는 다음 질문은 ViewModelProvider는 어떻게 ViewModel의 데이터를 보존할 수 있는 지일 것이다. 결론부터 말하자면 ViewModelProvider 역시 onDestory 과정에서 객체가 파괴되지만 ViewModelProvider로부터 가져오는 ViewModel 객체는 Configuration Change가 일어나기 이전의 객체를 동일하게 가져올 수 있다!

ViewModelProvider의 구현체를 보면서 어떻게 가능한 지 알아보도록 하자.

Implementation of ViewModelProvider

Constructor


public constructor(
    owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner))

// In Activity/Fragment
val viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]

일단 위의 코드를 보면 ViewModelProvider에 ViewModelStoreOwner 패러미터를 넣는 것을 볼 수 있는데 이는 Activity에서 this로 넣고 있다. 이는 어떻게 가능한 것인가?

ViewModelStoreOwner

ComponentActivity 소스 코드를 잘 보면 Activity가 ViewModelStoreOwner 인터페이스를 구현하고 있는 형태인 것을 볼 수 있다.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller

자 그렇다면 ViewModelStoreOwner가 Configuration Change가 일어나도 상태보존을 시킬 수 있는 ViewModel을 만들어주는 데 협력하는 기능임을 알 수 있다.

The responsibility of an implementation of the interface ViewModelStoreOwner is to retain owned ViewModelStore during the configuration changes and call ViewModelStore.clear(), when this scope is going to be destroyed.

ViewModelStoreOwner의 역할은 Configuration Change가 일어나도 ViewModelStoreOwner를 유지하고 이 Scope가 파괴될 경우 ViewModelStore.clear()를 호출하는 것이다.

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStore

그렇다면 Activity에서 getViewModelStore()는 어떻게 구현되었을까?

@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;
}

getViewModelStore()를 호출하는 경우

  • Activity가 Application에 부착되어 있지 않은 경우(onCreate() 이전 시점) Exception을 터뜨린다
  • 아니면 ensureViewModelStore를 호출한다
  • 그리고 Activity 내 멤버변수인 mViewModelStore를 리턴한다
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();
        }
    }
}

ensureViewModelStore에서는 mViewModelStore가 없는 경우에서만 로직이 수행된다. 우선 NonConfigurationInstances 객체를 가져오고 null이 아닌 경우 이를 통해 viewModelStore를 가져오고 null이면 ViewModelStore 생성자를 활용하여 ViewModelStore를 생성한다.

NonConfigurationInstances

그렇다면 NonConfigurationInstances 클래스는 어떻게 이전 상태의 ViewModelStore를 줄 수 있을까? 왜냐하면 이 객체는 Activity에 종속되지 않은 static(싱글턴) 클래스이기 때문이다. 이는 Application이 살아있는 동안 혹은 일부러 지우지 않는 이상 메모리에서 계속 남아있을 수 있다.

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

그렇다면 마지막 남아있는 의문은 이 static 클래스는 ViewModelStore를 언제 파괴시킬 수 있는 지 알 수 있는 것일까? Configuration Change나 일반 onDestory 호출하는 것이나 별반 다를 것이 없는데 말이다.

이는 ComponentActivity의 기본 생성자를 보면 알 수 있을 것이다.

        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();
                    }
                }
            }
        });

ComponentActivity에는 onDestory의 LifeCycleEventObserver를 부착하고 있다.

이때, Configuration Change가 일어나지 않는 경우에는 ViewModelStore 객체를 메모리에서 지우고 이후에 새로운 객체가 생성된다

만약 Configuration Change가 일어난 경우라면 싱글턴에 있는 ViewModelStore를 지우지 않는다.

이와 같은 방식으로 ViewModelProvider는 Configuration Change로 인한 onDestroy와 일반 onDestory를 구별할 수 있는 것이다.

원문

ViewModel magic revealed!!!

profile
이현우의 개발 브이로그

0개의 댓글