Learn & Run

Jetpack - VIewModel에 대해 자세히 알아보자 본문

Android

Jetpack - VIewModel에 대해 자세히 알아보자

iron9462 2021. 8. 29. 23:39

이번 포스팅에서는 Jetpack에 포함된 ViewModel 라이브러리에 대해 소개합니다. 앞서 설명한 Room처럼 대중적으로 많이 사용되는 라이브러리입니다. ViewModel에서 한 번 알아보도록 합니다.

ViewModel이란 무엇인가?

 

우리는 앱을 개발할 때, 단순한 기능을 구현하는 것 이상으로 앱의 아키텍쳐 설계에 대해서도 고민을 하곤 합니다. 그러면서 접하게되는 여럿 디자인 패턴중에서 MVVM(Model - View - ViewModel)에 대해 들어본 적이 있을 것 입니다. 저는 처음에 MVVM의 ViewModel과 Jetpack에 포함된 ViewModel이 같은 건지 다른 건지 헷갈려했던 기억이 있습니다. 사실 완전히 다른 차원의 개념인데도 불구하고 말입니다.

 

MVVM에서의 ViewModel은 설계시 요구되는 또는 정의된 컴포넌트라고 생각하면 되고, Jetpack의 ViewModel는 앱의 생명주기를 고려하여 UI 관련 데이터를 저장하고 관리하는데 도움을 주는 라이브러리에 포함된 클래스라고 생각하면 이해하기 쉬울 것 입니다. Jetpack의 ViewModel은 Android Architecture ViewModel로 불리는데, AAC ViewModel로 줄여 사용하기도 합니다.

 

아래 링크를 통해서 ViewModel의 전반적인 내용에 대해 참고하면 좋습니다.

 

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko 

 

ViewModel 개요  |  Android 개발자  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

 

 

ViewModel을 사용하는 이유

 

MVVM 아키텍쳐에서의 ViewModel은 View로부터 독립적입니다. 여기 독립적이란 의미는 ViewModel은 View를 참조하지 않고 View에서 필요한 데이터만을 소유하고 관리하게 됩니다.

 

Android에서 MVVM 디자인 패턴을 적용하면 View에서의 UI Controller(Activity, Fragment)가 과도한 책임을 분담하여 클래스가 거대해지는 것을 방지하고, 유지보수, 재사용성 그리고 테스트 등을 용이하게 만들어 준다. Android 개발시 가장 기본적으로 접하게되는 MVC 패턴 (Activity나 Fragment가 Controller 역할)으로 프로젝트를 진행하다가 MVVM 패턴으로 재설계 해보면 위에서 말한 장점이 이해가 될 때가 올 것 입니다.

 

아래 그림의 ViewModel의 Scope는 Activity의 생명주기 전체를 아우르고 있습니다. 이것은 Activity의 생성부터 파괴 될 때까지 ViewModel은 살아있다는 것을 의미하는데 Fragment 역시 Activity가 있는 상태에서 생성되는 것이기 때문에 ViewModel은 Fragment의 생명주기까지도 아우르고 있다고 볼 수 있습니다.

 

아래의 그림에서 Activity나 Fragment의 생명주기에서 일어나는 콜백 메서드를 확인할 수 있습니다. 그동안 우리들은 안드로이드 개발을 하면서 생명주기에 따른 데이터 보존에서 수많은 노력을 해왔을지도 모르지만, ViewModel이 가지고 있는 강점을 이용해보면 코드 구성이나 복잡도 등에 있어서 간결하게 처리할 수 있을 것입니다.

 

ViewModel 관련 클래스 확인해보기

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java)

        viewModel.doSomething()
    }
}

class MainViewModel: ViewModel() {

    fun doSomething() {
        println("Hello World!")
    }
}

 

우리는 위와 같이 ViewModel을 생성하곤 합니다. 근데 ViewModel을 어떻게 생성하게 되는지, 내부적으로 어떻게 관리되는지를 확인해 볼 필요가 있습니다. 첫 번째 파라미터로 this가 의미하는 것은 무엇일까요?

 

아래에서 ViewModelProvider 생성자를 확인하면 알 수 있겠지만, 우리는 ViewModelProvider의 생성자 첫 번째 파라미터로 ViewModelStoreOwner를 넣어주고 있습니다. 어떻게 this 키워드를 사용하여 파라미터를 전달할 수 있을까요?? 바로 AppCompatActivity -> FragmentActivity -> ComponentActivity 까지 들어가보면 알 수 있습니다. ComponentActivity는 ViewModelStoreOwner 인터페이스를 구현하고 있는 것을 확인할 수 있습니다. 아래 이미지에서 어떤 메서드를 구현하고 있는지 확인해봅니다.

 

위의 이미지를 확인해보면 ViewModelStoreOwner 인터페이스를 구현한 메서드인 getViewModelStore()를 확인할 수 있습니다. 메서드의 의미를 짐작해보면 ViewModelStore를 가져오겠다는 것을 알 수 있을 것 입니다. 여기서, NonConfigurationInstances의 멤버 변수인 ViewModelStore가 있는지 없는지에 따라서 ViewModelStore를 생성하고 있는 것을 확인할 수 있습니다.

 

NonConfigurationInstances클래스는 위와 같습니다. 멤버인 ViewModelStore가 메모리에 올라가 있는지 체크해주고, 메모리에 올라가있지 않다면 new 키워드를 통해서 객체를 생성하고 있다는 것인데 이것은 즉, ViewModel을 저장할 수 있는 저장소를 만든다는 의미인 것 같습니다!

 

이쯤에서 생성자를 살펴보면 될 것 같습니다. 위의 코드에서 사용한 ViewModelProvider 생성자를 확인해보니 아래와 같이 2가지의 경우가 있습니다.

 

1. 파라미터로 ViewModelStoreOwner만 받는 경우

2. 파라미터로 ViewModelStoreOwner와 Factory를 받는 경우

 

MainActivity에서 this로 넘겨줬지만 실제로 생성자가 생성될때는 this.getViewModelStore()를 하고 있습니다. 즉 ViewModelStore를 지정해준것과 마찬가지입니다. 이 ViewModelStore는 멤버로 지정해주고 있습니다. Factory도 마찬가지군요.

 

멤버함수 get을 살펴보면 파라미터로 넘겨준 modelcClass의 getCanonicalName 멤버함수를 호출하여 null 체크를 해준다. 여기서 getCanonicalName()은 사용자에게 잘 알려져있는 표준적인 이름을 반환하는 Class의 멤버 함수입니다. 파라미터 내용을보면 modelClass는 실제로 ViewModel을 생성할 수 있는 클래스의 이름을 나타내는 파라미터라고 명시되어 있습니다. 그러면 다시 return에서 사용되는 get 멤버 함수를 따라가 봅시다.

위의 이미지를 살펴보니 ViewModelStore에 Key를 이용하여 ViewModel을 가져오고 있는 것을 확인할 수 있습니다. 뭔가 익숙한 자료구조가 떠오르게 되는데 어떤 자료구조로 ViewModel을 저장하고 있는지는 아래 이미지를 확인할 수 있습니다. ViewModel이 있는지 없는지에 따라서 기존 것을 사용하거나, Factory를 이용하여 새로운 ViewModel을 생성하고 있는 것을 확인할 수 있습니다. 그리고 마지막으로 ViewModelStore에 put 멤버 함수로 Key와 ViewModel을 넣어 저장시켜주고 있습니다. 이러한 코드를 살펴보면서 앞서 살펴본 ViewModel의 생명주기가 어떤식으로 관리되고 있는지 확인하실 수 있을 것 입니다.

 

앞에서 추측해봤던 ViewModelStore가 어떤식으로 구성되어 있는지 확인해 보겠습니다. 역시나 멤버로 HashMap을 사용하고 있습니다. 기존에 알고 있던 HashMap을 다루는 것과 사용법은 비슷해서 어려움이 없을 것으로 생각됩니다.

 

원문 - If an owner of this ViewModelStore is destroyed and is not going to be recreated, then it should call clear() on this ViewModelStore, so ViewModels would be notified that they are no longer used.  

번역 - ViewModelStore의 Owner가 파괴되고 다시 생성되지 않을 경우 clear()를 호출해야 합니다. 따라서 ViewModel은 더 이상 사용되지 않는다는 알림을 받습니다.

 

ViewModelStore에 저장된 ViewModel은 당연히 사용하지 않을때 파괴해야 할 것입니다. 그러면 어떻게 이러한 처리를 하고 있는 것일지 궁금하실 것 같습니다.

 

ComponentActivity의 생성자 로직을 살펴보면, getLifeCycle()에 Observer를 등록해준 것을 확인할 수 있습니다. LifecycleEventObserver가 LifeCycler.Event를 감지하고 있다가 ON_DESTROY인 상태가 됐을 때, ViewModelStord의 멤버 함수인 clear()를 통해서 메모리를 해제하게 됩니다.