1. AAC ViewModel이란?
AAC ViewModel(이하 ViewModel)은 ViewModelStoreOwner의 수명주기를 인식하며, 화면 회전같은 configuration change에서 데이터가 생존할 수 있도록 해준다.
또한 목적에 따라 ViewModel, onSaveInstanceState, Persistent Storage를 적절히 이용해 데이터를 저장할 수 있도록 한다. 이는 저장소 위치, 시스템에서 프로세스 중단시에도 데이터 유지, 사용자가 액티비티를 완전히 닫거나 finish()등 에서도 데이터가 유지되는지에 따른 차이가 있으니 필요에 맞게 단독 또는 조합하여 사용해야 한다.
ViewModel은 onSaveInstanceState나 Persistent Storage와는 다르게 메모리에 데이터를 유지하기 때문에 데이터를 Read/Write 속도가 빠르다.
ViewModel은 위의 사진처럼 화면이 회전하거나, onDestroy가 호출되며 onCleared를 호출하기 전까지 살아있을 수 있다.
액티비티의 경우 ComponentActivity.java 에서 getLifecycle().addObserver을 통해 Lifecycle의 상태가 ON_DESTROY이며, Configuration이 변경(화면 회전과 같은)이 일어나지 않았을 경우 ViewModelStore에 접근해 clear()함수를 실행하고, ViewModelStore안에 있는 ViewModel의 해시값(정보)를 지우게 된다.
이와 같은 동작으로 인해 화면 변경에도 데이터가 살아남을 수 있다.(isChangingConfigurations()가 거짓이라면 getViewModelStore.clear()가 호출되지 않기때문이다.)
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();
}
}
}
});
//ViewModelStore.java
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
ViewModel의 좀 더 정확한 내부의 구조에 따른 실행은 아래 블로그를 정독해보자.(생성부터 파괴까지에 대해 잘 설명이 되어있다!)
https://pluu.github.io/blog/android/2020/05/04/viewmodel-b-to-d/
2. ViewModel 사용법
dependencies {
implementation 'androidx.activity:activity-ktx:1.2.2'
implementation 'androidx.fragment:fragment-ktx:1.3.3'
.. }
먼저 모듈 수준의 gradle에 두 항목을 implement 해준다.
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
ViewModel()을 위의 예제와 같이 상속해주고,
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val model: MyViewModel by viewModels()
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
}
}
ViewModel 변수를 선언할 때 by ViewModels()를 붙여주면 액티비티가 다시 만들어져도 같은 인스턴스를 가진 MyViewModel을 호출할 수 있다. 액티비티가 완전히 종료된다면 ViewModel의 리소스는 onCleared()에서 지워진다.
ViewModelStore은 이름 그대로 ViewModel을 저장하는 클래스인데, 이곳에서 clear를 호출해주기도 한다. 생명주기가 종료될 때 clear()함수를 호출하여 ViewModel이 종료되게끔 동작한다.
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared(); // onCleared()를 호출한다.
}
}
...
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear(); // ViewModel의 clear()함수 내부에 onCleared()를 호출한다.
}
mMap.clear();
}
}
3. activityViewModels는 어떨 때 사용할까?
먼저 액티비티에서는 activityViewModels를 호출할 수 없다. 오직 프래그먼트에서만 사용할 수 있다. 프래그먼트가 생성된 액티비티의 라이프 사이클에 ViewModel을 종속시키고 싶을 때 사용한다.
액티비티에 여러개의 프래그먼트를 가지고 같은 데이터를 공유하고 싶다면, 또는 프래그먼트에서 처리한 데이터를 activity에서 데이터 갱신을 하고싶은 경우 이용해주면 된다.
- 액티비티에 여러개의 프래그먼트가 존재하는 경우, 프래그먼트는 각각의 생명주기에 맞는 ViewModel을 가질 수 있는데, 이는 서로 다른 인스턴스를 가지게 된다.
- 하지만 여러개의 프래그먼트가 같은 인스턴스를 통해 같은 데이터를 이용하고 싶은 경우, SharedViewModel을 만들어 같은 인스턴스를 가지고 데이터를 공유할 수 있는데 이는 activityViewModels을 통해 가능하다.
- 이렇게 생성된 ViewModel을 액티비티에서는 viewModels()를 통해 이용하며, 같은 인스턴스를 가리킨다.
val mainViewModel : MainViewModel() by activityViewModels()
그렇다면 어떻게 같은 액티비티의 데이터를 공유할 수 있을까?
@MainThread
public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
VM::class, { requireActivity().viewModelStore },
factoryProducer ?: { requireActivity().defaultViewModelProviderFactory }
)
activitiyViewModels의 구현을 보면 requireActivity를 통해 프래그먼트의 부모 액티비티를 얻게된다. 그 얻게된 부모 액티비티를 통해 Activity.viewModelStore에서 모델의 해시값을 동일하게 유지하기 때문이다.
즉, ViewModel의 인스턴스가 둘 다 같기 때문에 같은 데이터를 유지할 수 있게 된다.
4. 출처
https://developer.android.com/topic/libraries/architecture/viewmodel
https://pluu.github.io/blog/android/2020/05/04/viewmodel-b-to-d/
https://kotlinworld.com/88?category=918952
'Android' 카테고리의 다른 글
Drawble Selector를 이용한 Button 만들기 (0) | 2022.09.14 |
---|---|
LifecycleScope, ViewModelScope의 내부 구조 (0) | 2022.09.14 |
Fragment의 Lifecycle 잘 알고 있나요 (0) | 2022.09.13 |
AAC ViewModel 따라가기 (0) | 2022.09.13 |
Coroutine과 Retrofit을 같이 사용하면 enqueue를 쓰지 않아도 되는 이유는? (3) | 2022.09.13 |