안드로이드는 리소스에 대한 제약이 많은 OS이다.
그렇기 때문에 View가 Destroy 되어도 유지되어야 하는 데이터가 있다면 UI Controller (Activity, Fragment)의 Life cycle에 따라 데이터가 파괴되기 전에 OnSavedInstanceState
등의 메소드를 오버라이드 받아 데이터를 Bundle에 저장한뒤 다시 onRestoreInstanceState
를 이용해 복구해야한다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Activity가 Destory되기 전 저장하고 싶은 데이터를 Bundle의 형태로 onCreate
에 넘겨주면 데이터를 날리지 않고 계속 이용할 수 있는 것이다.
하지만 이러한 방법에는 다음과 같은 문제점이 있다.
onCreate
에서 작업을 처리해야 하므로 UI 컨트롤러가 해야 할 일이 늘어나게 되고 화면을 띄우는데 시간이 오래 걸리게 된다.Activity나 Fragment와 같은 UI 컨트롤러에서 데이터를 관리하면 생명 주기에 따라서 값이 사라지는 문제가 발생하고 이 문제를 해결하기 위해 saveInstanceState
로 데이터를 저장하려면 위와 같은 문제가 존재한다.
따라서 이러한 문제를 해결하기 위해 나온것이 바로 ViewModel이다.
ViewModel은 데이터가 Activity에서는 Activity가 완전히 종료될 때까지, Fragment에서는 Fragment가 분리될 때까지 메모리에 남아있도록 설계 되어있다.
UI Controller의 finish()
호출등에 의해 생명주기가 종료됨에 따라 내부의 LifecycleEventObserver를 통해 ViewModel도 onCleared() 콜백 메서드를 호출하고 종료된다.
ViewModelProvider
는 ViewModel 인스턴스를 생성하기 위해 사용하는 클래스이다.
ViewModelProvider
는 생성자 매개변수로 ViewModelStoreOwner
를 필요로 한다.
액티비티나 프래그먼트 내에서 ViewModelProvider 객체를 통해 viewmodel 인스턴스를 얻을 수 있다.
class MainActivity : AppCompatActivity(), View.OnClickListener {
myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
...
}
위 코드에서 this
는 LifecycleOwner로서, 해당 생명주기에 맞추어 viewmodel이 관리된다.
ViewModelStoreOwner
는 인터페이스의 형태로 ViewModelStore
를 제공하는 역할을 하고 있다. (ViewModelStore
는 아래에서 자세히 설명하겠지만, 간단하게는 ViewModel의 인스턴스를 관리하는 클래스라고 할 수 있다.)
ViewModelStoreOwner가 구현된 코드를 살펴보면 ViewModelStore 객체를 반환하는 함수를 정의하고 있는 것을 확인할 수 있다.
ViewModelProvider는 ViewModelStoreOwner를 생성자 매개변수로 받아 ViewModelStore를 얻고, 이를 사용해 ViewModel 인스턴스를 저장하고 관리하는 것이다.
그럼 ViewModelProvider는 생성자의 매개변수로 ViewModelStoreOwner를 전달받는다고 했는데 위에서 ViewModel 인스턴스를 생성하는 예제에서는 this를 넘겨주고 있었다. 그렇다면 ViewModelStoreOwner와 Activity, Fragment는 무슨 관계일까?
이에 대한 해답은 Activity의 부모 클래스인 ComponentActivity(AppCompatActivity)
에서 찾을 수 있다.
ComponentActivity
의 내부 코드를 살펴보면 ViewModelStoreOwner를 구현하고 있다는 것을 알 수 있다.
Fragment 역시 부모 클래스에서 ViewModelStoreOwner를 구현하고 있다.
즉, Activity와 Fragment가 ViewModelStoreOwner를 구현하기 때문에 ViewModel 객체를 생성할 때 어떤 Owner를 전달하느냐에 따라 ViewModel의 Scope이 정해진다.
공식문서에 따르면 ViewModelStore의 역할은 다음과 같이 정리되어있다.
ViewModelStore는 ViewModel 인스턴스를 저장하기 위한 클래스이다. AAC 관점에서의 ViewModel의 근본적인 역할은 Configuration Change가 발생해도 데이터를 유지하고 파괴되지 않도록 보장하는 것이다. 즉, ViewModel 인스턴스를 ViewModelStore에 저장해두고 Configuration Change가 발생해 UI Controller가 재생성되는 시점에, ViewModelStore에 저장된 ViewModel 인스턴스를 다시 가져와 사용할 수 있도록 한다.
ViewModelStore 클래스가 구현된 코드를 살펴보면 HashMap<String, ViewModel>
의 형태로 ViewModel을 관리하고 있다. 그리고 get함수에 key값을 전달해 mMap의 value (ViewModel 인스턴스)를 반환하고 있다.
그렇다면 ViewModel의 인스턴스는 왜 Map의 구조를 가지고 있을까?
ViewModelProvider
는 Owner(Activity나 Fragment)
를 기반으로 ViewModel
을 생성하고 관리한다. 여기서 중요한 점은, 서로 다른 Owner(예: 다른 Activity나 Fragment)에서 동일한 ViewModel 클래스를 사용하더라도, 다른 ViewModel 인스턴스를 생성하게 된다.
val viewModel1 = ViewModelProvider(activity1).get(MyViewModel::class.java)
val viewModel2 = ViewModelProvider(activity2).get(MyViewModel::class.java)
위 코드를 보면, activity1과 activity2는 동일한 MyViewModel 클래스를 사용하지만, 서로 다른 Owner에 의해 생성된 ViewModel은 별개의 메모리 공간을 사용하기 때문에 다른 객체가 된다.
즉, ViewModel의 인스턴스는 Owner별로 독립적이며, 다른 메모리 공간을 사용하여 서로 다른 객체로 존재하는 다른 인스턴스라는 것이다.
정리하자면, Owner별로 고유한 값(Key)를 사용해 ViewModel 인스턴스를 관리하기 위해서 ViewModelStore에서는 ViewModel을 Map의 구조로 관리하고 있는 것이다.
마지막으로 ViewModelFactory이다.
ViewModel은 기본적으로 파라미터가 없는 생성자를 사용해 인스턴스화된다. 하지만 ViewModel 객체를 생성할 때 특정한 매개변수가 필요한 경우가 있다. 이때
ViewModelFactory는 ViewModel을 만들기 위한 Factory(공장) 역할을 합니다. 기본 ViewModel 생성자에서는 인자를 받을 수 없으므로, ViewModelFactory를 통해 원하는 인자를 전달하고 해당 인자를 사용하여 ViewModel을 생성할 수 있습니다.
ViewModelFactory를 만들려면 ViewModelProvider.Factory 인터페이스를 구현해야 한다.