안드로이드 개발자 노트
[Android] MVVM AAC ViewModel 잘 알아보기 본문
안녕하세요. 이번 포스팅에서는 ViewModel에 대해서 알아보도록 하겠습니다.
ViewModel 개념
ViewModel 은 비즈니스 로직 또는 화면 수준 상태(State) 홀더 클래스입니다.
UI에 상태를 노출하고 관련 비즈니스 로직을 캡슐화합니다.
주요 이점은 상태를 캐시하여 구성 변경에도 이를 유지한다는 것입니다.
즉, 액티비티 간에 이동하거나 구성 변경(화면 회전 등)을 따를 때 UI가 데이터를 다시 가져올 필요가 없습니다.
ViewModel의 이점
UI에 표시되는 데이터를 저장하는 일반 클래스를 사용하면 액티비티 또는 프래그먼트 사이를 이동할 때 문제가 될 수 있습니다.
예를 들어, 화면 회전이 이루어지게 될 때, Activity가 파괴(onDestroy)된 다음 다시 화면이 만들어지면서(onStart) 복구 로직이(onRestoreInstanceState) 수행됩니다.
Lifecycle에 따라 파괴되고 복구되어야 하는 데이터가 있다면 파괴 전에 데이터를 저장(savedInstanceState)했다가 꺼내와야 하는 번거로움이 있습니다.
만약 Activity가 살아있는 동안은 계속해서 데이터를 유지할 수 있는 방법이 있다면 굳이 저장 후에 다시 복구하지 않아도 될 것입니다.
즉, Activity의 Lifecycle보다 긴 수명을 가지는 인스턴스가 있을 수 있다면 이를 해결할 수 있습니다.
위의 그림과 같이 ViewModel은 액티비티 혹은 프래그먼트와는 다른 생명주기를 가지고 있습니다.
액티비티 혹은 프래그먼트가 종료(finish)되었을 때 ViewModel은 onCleared 메서드를 통해 소멸이 됩니다.
이는 곧, ViewModel이 더 긴 생명주기를 갖고 있다는 얘기로, 위의 문제점들을 보완할 수 있게 됩니다.
여기서 Activity rotated와 finish사이의 onDestroy, 그리고 finished되기 직전의 onDestroy를 ViewModel은 어떻게 구분하는지 의문이 생깁니다.
이는 ComponentActivity 내부의 LifeCycle Observer를 보면 알 수 있습니다.
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();
}
}
}
});
액티비티의 생명주기를 관찰하는 Observer가 있고, 생명주기 이벤트가 onDestroy일 때 isChangingConfiguration이 아니라면 ViewModelStore를 Clear해주는 것을 알 수 있습니다.
ViewModel 구현
다음은 ViewModel 구현의 예입니다. ViewModel을 상속받아 클래스를 작성합니다.
class MyViewModel : ViewModel() {
private val _users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers()
}
}
val users: LiveData<List<User>> = _users
fun setUsers(userLise: List<User>) {
_users.value = userLise
}
// Model쪽에 List<User> 값을 요청하는 메서드
fun loadUsers() {
// 유저를 불러오는 기능...
}
}
data class User (val name: String = "")
위의 예제에서는 MutableLiveData 타입의 _users 프로퍼티를 선언하고 users라는 LiveData에서 이 값을 받습니다. 액티비티나 프래그먼트에서는 users 프로퍼티에 접근해 사용자에게 보여주기만 하면 됩니다.
ViewModel의 핵심은 ViewModel안에서 그 데이터를 처리하고 가공하는 작업을 하는 것입니다.
이렇게 선언된 ViewModel은 액티비티에서 아래와 같이 사용됩니다.
class MyActivity : AppCompatActivity() {
// 시스템이 액티비티의 onCreate를 처음 호출할 때 뷰모델 객체는 생성
// re-created되는 액티비티는 처음 액티비티가 만들어질 때 생성된 뷰모델 객체를 다시 받습니다
val model by lazy { ViewModelProvider(this@MainActivity)[MyViewModel::class.java] }
override fun onCreate(savedInstanceState: Bundle?) {
model.users.observe(this, Observer<List<User>>{ users ->
// update UI
})
}
}
ViewModel을 인스턴스화할 때는 ViewModelProvider라는 클래스를 사용해 ViewModel객체를 가져옵니다. ViewModelProvider 클래스의 인자로 ViewModelStoreOwner 인터페이스를 구현(implement)하는 객체(this@MainActivity)를 전달합니다. 이를 통해 액티비티 혹은 프래그먼트는 기본적으로 ViewModelStoreOwner를 구현(implement)하고 있다는 사실을 알 수 있습니다.
ViewModelProvider 클래스의 내부를 보면 다음과 같습니다.
public open class ViewModelProvider(
private val store: ViewModelStore,
private val factory: Factory
) {
public interface Factory {
public fun <T : ViewModel> create(modelClass: Class<T>): T
}
...
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner))
...
public constructor(
owner: ViewModelStoreOwner,
factory: Factory
) : this(owner.viewModelStore, factory)
...
}
두 개의 부생성자가 있으며,
ViewModelStoreOwner만 받는 생성자와 ViewModelStoreOwner 그리고 Factory를 받는 생성자가 있습니다.
Factory는 인터페이스이며, ViewModel 인스턴스를 만들어주는 create 메서드를 담고있습니다.
ViewModelProvider 내부를 살펴보면 어떻게 ViewModel은 Activity의 LifeCycle보다 긴 수명주기를 가질 수 있는지 알 수 있습니다.
예제의 MainActivity를 보면 ViewModelProvider를 다음과 같이 인스턴스화하고 있습니다.
val model by lazy { ViewModelProvider(this@MainActivity).get(MyViewModel::class.java) }
ViewModelProvider의 get 메소드를 살펴보면 다음과 같습니다.
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
파라미터로 받은 ViewModel 클래스가 isInstance면 그대로 리턴시켜주고 그렇지 않다면 create해주는 것을 볼 수 있습니다.
다음으로 ViewModelProvider의 파라미터로 받은 ViewModelStoreOwner 인터페이스의 내부를 보면 다음과 같습니다.
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
ViewModelStoreOwner는 단일 메서드 인터페이스이며 ViewModelStore를 제공하고 있습니다.
또 ViewModelStore의 내부는 다음과 같습니다.
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();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
ViewModelStore 클래스는 ViewModel을 HashMap 형태로 관리하고 있습니다.
위 클래스와 인터페이스들을 정리해보면 다음과 같습니다.
1. ViewModelProvider를 통해 ViewModel 인스턴스를 요청한다.
2. ViewModelProvider 내부에서는 ViewModelStoreOwner를 참조하여 ViewModelStore를 가져온다.
3. ViewModelStore에게 이미 생성된(저장된) ViewModel 인스턴스를 요청한다.
4. 만약 ViewModelStore가 적합한 ViewModel 인스턴스를 가지고 있지 않다면, Factory를 통해 ViewModel인스턴스를 생성한다.
5. 생성한 ViewModel 인스턴스를 ViewModeStore에 저장하고 만들어진 ViewModel 인스턴스를 클라이언트에게 반환한다.
6. 똑같은 ViewModel 인스턴스 요청이 들어온다면, 1~3번의 과정을 반복하게 된다.
권장사항
다음은 ViewModel을 구현할 때 따라야 할 권장사항입니다.
ViewModelStoreOwner보다 오래 지속될 수 있으므로 ViewModel은 메모리 누수를 방지하기 위해 Context 또는 Resources와 같은 수명 주기 관련 참조를 해서는 안 됩니다.
이상으로 포스팅을 마치겠습니다.
감사합니다.
'Android' 카테고리의 다른 글
[Android] Android KTX 잘 알아보기 (0) | 2023.01.22 |
---|---|
[Android] MVVM AAC LiveData 잘 알아보기 (0) | 2023.01.08 |