ViewModel이란?
ViewModel이란 Android Jepack의 구성요소 중 하나로, 본래 ViewModel이란 이름은 소프트웨어 개발 디자인 패턴중 하나인 MVVM(Model – View – ViewModel) 디자인 패턴으로부터 파생되었다.
MVVM의 관점에서 부르는 ViewModel과 Android Jetpack에 포함된 ViewModel 클래스를 구분하기 위해 흔히 Android Jetpack에 포함된 ViewModel을 Android Architecture ViewModel의 약자인 AAC ViewModel이라고 부르기도 한다.
ViewModel은 왜 필요하지?
MVVM의 관점에서 봤을 때 ViewModel은 View로부터 독립적이며, View가 필요로 하는 데이터만을 소유한다. 안드로이드 앱 개발시에도 MVVM 디자인 패턴을 적용하면 Activity나 Fragment 같은 UI컨트롤러의 과도한 책임을 분담하여 클래스가 거대해지는 것을 방지하고, 유지보수, 재사용성 그리고 테스트 등을 용이하게 만들어 준다.
구글에서도 앱 개발자들에게 MVVM패턴을 사용을 권장하고 있으며, MVVM관점의 ViewModel을 구현할 때 AAC ViewModel을 사용하면 좋다.
ViewModel의 특징
ViewModel은 Activity에서는 Activity가 완전히 종료될 때까지, 그리고 Fragment에서는 Fragment가 분리될 때까지 메모리에 남아있도록 설계 되어있다. 이를 좀 더 자세히 알기 위해서 Activity 생명 주기에 대한 이해가 필요하다.
위의 그림을 보면 Activity의 생명주기와 ViewModel의 생명주기를 함께 확인할 수 있다. 액티비티가 최초 생성될 때 일반적으로 ViewModel을 인스턴스화 하여 생명주기를 함께 시작한다. (ViewModel의 생명주기를 Fragment의 생명주기와 함께하도록 만들 수도 있다.)
Configuration 변경이 (예:화면 회전) 발생할 때 액티비티가 다시 시작 되는 것을 확인할 수 있다. 하지만 ViewModel은 여전히 메모리 상에 남아있는다. 이는 Activity 내부에서 Configuration 변경과 무관하게 유지 되는 NonConfigurationInstances 객체를 따로 관리하기 때문이다.
Activity의 finish() 호출등에 의해 액티비티가 생명주기가 종료됨에 따라 내부의 LifecycleEventObserver를 통해 ViewModel도 onCleared() 콜백 메서드를 호출하고 종료된다.
ViewModel 요청 프로세스
- ViewModelProvider를 통해 ViewModel 인스턴스를 요청한다.
- ViewModelProvider 내부에서는 ViewModelStoreOwner를 참조하여 ViewModelStore를 가져온다.
- ViewModelStore에게 이미 생성된(저장된) ViewModel 인스턴스를 요청한다.
- 만약 ViewModelStore가 적합한 ViewModel 인스턴스를 가지고 있지 않다면,
Factory를 통해 ViewModel인스턴스를 생성한다. - 생성한 ViewModel 인스턴스를 ViewModeStore에 저장하고 만들어진 ViewModel 인스턴스를 클라이언트에게 반환한다.
- 똑같은 ViewModel 인스턴스 요청이 들어온다면, 1~3번의 과정을 반복하게 된다.
ViewModel의 구현
가장 먼저 해야 할 일은 ViewModel 클래스를 상속하는 서브 클래스를 정의한다.
class MainViewModel : ViewModel() { ... }
그 후에 다음과 같이 ViewModel 인스턴스에 접근할 수 있다.
class MainActivity : AppCompatActivity() { private lateinit var viewModel:MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //ViewModel 인스턴스 생성 viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java) } }
ViewModel을 생성하기 위해서는 ViewModel Provider 객체가 필요하다. 일반적으로 ViewModelProvider의 생성하기 위해 생성자 매개변수로 ViewModelStoreOwner와 ViewModelProvider.Factory가 필요하다.
ComponentActivity가 ViewModelStoreOwner 인터페이스를 구현하고 있으므로, ComponentActivity의 서브클래스인 AppCompatActivity를 사용하고 있다면 별도로 ViewModelStoreOwner를 구현할 필요는 없다. Fragment 또한 ViewModelStoreOwner를 구현하고 있다. ViewModel의 생명주기를 Fragment와 함께하길 원하는 경우에는 ViewModelProvider에서 생성자 첫번째인자로 ViewModelStoreOwner를 지정할 때 ComponentActivity 서브클래스 대신 Fragment를 넘겨주면 된다.
ViewModelProvider.Factory의 경우 ViewModelProvider 내에 선언된 ViewModelProvider.Factory의 서브 클래스를 사용할 수 있다. 생성자가 없는 ViewModel을 인스턴스화 하는 경우에만 간단히 NewInstanceFactory 클래스를 사용하고, 그 외에는 직접 ViewModelProvider.Factory를 구현해야 한다.
ViewModel의 경우 application을 멤버로 갖는 AndroidViewModel이라는 클래스가 이미 정의되어있다. 이 클래스를 사용하는 경우 ViewModelProvider.AndroidViewModelFactory를 사용하여 AndroidViewModel을 다음과 같이 인스턴화 할 수 있다.
viewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application)) .get(MainViewModel::class.java)
ViewModel을 이용한 Fragment간의 Data 공유
액티비티내에서 프레그먼트간의 데이터 또는 이벤트를 공유하는 것은 일반적이다.
ViewModel을 사용하면 이를 좀 더 쉽게 해결할 수 있다
앞의 그림을 살펴보면 SharedViewModel에 두 Fragment가 동시에 접근하고 있다. SharedViewModel을 인스턴스를 얻을 때 ViewModelProvider의 첫번째 생성자 매개변수인 ViewModelStoreOwner를 Fragment들이 속해있는 부모 Activity로 지정하자. 그러면 각 프레그먼트는 동일한 SharedViewModel 인스턴스를 얻게 된다. SharedViewModel의 생명주기는 Activity를 따르게 되고, Fragment의 생명주기는 Activity의 서브셋이므로 Fragment의 생명주기 동안에 자유롭게 데이터를 공유할 수 있게 된다.
코드를 살펴보자.
class SharedViewModel : ViewModel(){ val count = MutableLiveData<Int>().apply { value = 0 } }
class MasterFragment : Fragment(){ private lateinit var viewModel : SharedViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activity?.run { viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()) .get(SharedViewModel::class.java) } } }
class DetailFragment : Fragment(){ private lateinit var viewModel : SharedViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activity?.run { viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()) .get(SharedViewModel::class.java) } } }
Activity범위내에서 ViewModel을 공유하여 프레그먼트가 데이터를 공유하는 경우 다음과 같은 장점을 갖는다. (공식문서 참조)
- Activity는 Fragment 커뮤니케이션에 개입하지 않아도 된다.
- Fragment는 SharedViewModel외에 다른 부분에 대해서 의존하지 않아도 된다. 프레그먼트 중 하나가 사라져도 다른 프레그먼트는 평소처럼 동작한다.
- 각 프레그먼트는 자체 생명주기를 가지고 있고, 다른 프레그먼트의 생명주기에 영향을 주거나 받지 않는다.
결국 Activity와 Fragment들이 강하게 결합되어있던 부분을 느슨하게 만드는 효과를 주면서 유지보수나 확장성 측면에서 많은 이득을 볼수 있게 된다.
예제코드는 github에서 확인할 수 있다.
TL;DR
ViewModel을 사용하여 관심사분리를 하고, Activity 재생성시에도 데이터를 유지하며, Fragment간 데이터를 공유하는 방법에 대해서 알아보았다.
이미 별도의 솔루션이 있다면 ViewModel을 사용하지 않아도 좋다. 복잡해보일지 몰라도 구현하는 방법은 간단하므로 몇줄의 코드로 여러가지 장점을 얻을 수 있으니 ViewModel사용을 강력하게 권장한다.
0개의 댓글