ViewModel
- ViewModel은 앱의 Lifecycle을 고려하여 UI 관련 데이터를 저장하고 관리하는 컴포넌트입니다.
ViewModel은 왜 필요할까?
- ViewModel은 View로부터 독립적이며, View가 필요로 하는 데이터만을 소유한다.
- UI Controller로부터 UI 관련 데이터 저장 및 관리를 분리하여 ViewModel이 담당하도록 하면 다음과 같은 문제를 해결할 수 있습니다.
- 안드로이드 프레임워크는 특정 작업이나 완전히 통제할 수 없는 기기 이벤트에 대한 응답으로 UI Controller를 제거하거나 다시 생성할 수 있는데, 이 경우 UI Controller에 저장된 모든 일시적인 관련 UI 데이터가 삭제됩니다.
- UI Controller에서 데이터를 위한 비동기 호출을 한다면 메모리 누수 가능성을 방지하기 위한 많은 유지 관리가 필요합니다.
- UI Controller에서 DB나 네트워크로부터 데이터를 로드하도록 하면 단일 클래스가 많은 작업을 처리하게 될 수도 있습니다. 이러면 테스트가 훨씬 어려워지게 됩니다.
ViewModel의 특징
- ViewModel은 Activity에서는 Activity가 완전히 종료될 때 까지, 그리고 Fragment에서는 Fragment가 분리될 때 까지 메모리에 남아있도록 설계되어 있다.
- ViewModel 객체는 뷰 또는 LifecycleOwners보다 오래 지속되도록 설계되었으며, ViewModelProvider에 전달되는 LifecycleOnwers에 따라 Lifecycle이 달라집니다.
- 만약 액티비티가 전달되었다면 액티비티가 종료될 때까지, 프래그먼트가 전달되었다면 프래그먼트가 분리될 때까지 ViewModel은 메모리에 남아 있습니다.
ViewModel 요청 프로세스
- ViewModelProvider를 통해 ViewModel 인스턴스를 요청한다.
- ViewModelProvider 내부에서는 ViewModelStoreOwner를 참조하여 ViewModelStore를 가져온다.
- ViewModelStore에게 이미 생성된(저장된) ViewModel 인스턴스를 요청한다.
- 만약 ViewModelStore가 적합한 ViewModel 인스턴스를 가지고 있지 않다면, Factory를 통해 ViewModel 인스턴스를 생성한다.
- 생성한 ViewModel 인스턴스를 ViewModelStore에 저장하고 만들어진 ViewModel 인스턴스를 클라이언트에 반환한다.
- 똑같은 ViewModel 인스턴스 요청이 들어온다면, 1~3번의 과정을 반복하게 된다.
ViewModel의 구현
1. kotlin의 by 키워드 이용
(해당 ViewModel이 초기화 되는 액티비티,혹은 프래그먼트의 생명주기에 완전히 종속)
private val viewModel: MyViewModel by viewModels()
- 보통은 1번의 방법을 많이 사용하지만, 프래그먼트들 사이에서 데이터 공유를 해야하는 상황이 나올때 2번을 사용한다.
2. ViewModelProvider를 이용하여 초기화
(지정한 컴포넌트의 생명주기에 종속)
val viewModel = ViewModelProvider(
this,
ViewModelProvider.NewInstanceFactory())
.get(MyViewModel::class.java)
-
기본적으로 ViewModel는 추상클래스로, new 키워드로 객체 생성이 불가하다. 그렇기때문에 ViewModelProvider을 통해 객체 생성을 해야 한다. 일반적으로 ViewModelProvider의 매개변수로 ViewModelStoreOwnwer와 ViewModelProvider.Factory가 필요하다.
-
ComponentActivity와 ComponentActivity의 서브클래스인 AppCompatActivity는 별도로 ViewModelStoreOwnwer 인터페이스를 구현하고 있다. Fragment 또한 마찬가지이다.
-
생성자에 매개변수가 있는 ViewModel을 인스턴스화 하는 경우에는 ViewModelProvider.Factory를 구현해야 한다.
(참고 : ViewModel은 액티비티 혹은 프래그먼트 내에서 1개만 생성이 가능하다. 즉, 액티비티 내의 싱글톤 객체로 아무리 여러번 생성해도 하나의 객체만 계속 사용된다.)
AndroidViewModel
- 기본적으로 ViewModel은 액티비티나 프래그먼트에 의존적인데, 개발을 하다보면 이를 넘어 Context를 사용해야 할 때가 있다. 이럴 때 액티비티나 프래그먼트를 참조하면 메모리 릭이 발생한다.
- 기본적으로 Activity와 Fragment보다 긴 생명주기를 가지고 있는데, 여기서 이에 대한 참조를 가지고 있다면, 아무리 사라져도 ViewModel은 사라지지 않고 메모리를 차지하고 있을 것이다.
- 그렇기 위해서 AndroidviewModel을 사용하는데, 이 클래스는 Application에 의존적이다. 즉, 특정 액티비티나 프래그먼트가 사라지더라도 인스턴스가 유지되고, Application이 종료되는 시점에 onCleared()가 호출되어 데이터가 사라지게 된다.
ViewModel을 이용한 Fragment간의 Data 공유
- 액티비티내에서 프래그먼트 간의 데이터 또는 이벤트를 공유하는 것은 일반적이다. ViewModel을 사용하면 이를 좀 더 쉽게 해결할 수 있다.
- SharedViewModel에 두 프래그먼트가 동시에 접근하고 있다. SharedViewModel 인스턴스를 얻을 때 ViewModelStoreOwnwer를 부모 Activity로 지정하자. 그러면 SharedViewModel의 생명주기는 Activity를 따르게 되고, Fragment의 생명주기는 Activity의 서브셋이므로 Fragment의 생명주기 동안에 자유롭게 데이터를 공유할 수 있게 된다.
- 다음과 같은 장점을 가질 수 있다.
- Activity는 Fragment 커뮤니케이션에 개입하지 않아도 된다.
- Fragment는 SharedViewModel 외에 다른 부분에 대해서 의존하지 않아도 된다.
- 각 프래그먼트는 자체 생명주기를 가지고 있고, 각자 영향을 주거나 받지 않는다.
-> 결국 Activity와 Fragment들이 강하게 결합되어 있던 부분을 느슨하게 만드는 효과를 주면서 유지보수나 확장성 측면에서 많은 이득을 볼수 있게 해준다.
ViewModel 코드
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
loadUsers()
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers(): MutableLiveData<List<User>> {
return MutableLiveData(listOf(User("name", "number")))
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val vm: MyViewModel by viewModels()
vm.getUsers().observe(this, Observer { users ->
//update UI
})
}
}
ViewModel을 생성하는 법