[Android] ViewModel의 기본 개념 및 사용법

wooongyee
8 min readNov 6, 2023

--

ViewModel 이란?

UI 관련 데이터를 관리하고 수명 주기에 따른 처리를 해주는 클래스

  • AAC(Android Architecture Component) 중 하나이다.
  • View로부터 View에 표현되는 데이터의 소유권을 분리한다.

왜 사용하는가?

UI 상태를 저장하고 관련 비즈니스 로직을 캡슐화하기 위해서

  • UI 상태를 저장한다 -> UI 컨트롤러와 데이터가 분리된다.
  • UI 컨트롤러들 간의 데이터 공유가 쉬워진다.
  • 비즈니스 로직을 분리하고 접근할 수 있도록 한다.

UI 컨트롤러 : 액티비티, 프래그먼트 등

ViewModel 사용하기

ViewModel을 초기화하는 방법은 3가지가 있다.

  1. ViewModelProvider 사용
  2. by viewModels() 사용
  3. by activityViewModels() 사용 — Fragment에서만 가능

Gradle 의존성 추가하기

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'  // 1 필수
implementation 'androidx.activity:activity-ktx:1.6.1' // 2
implementation 'androidx.fragment:fragment-ktx:1.6.1' // 3

app 수준 build.gradle(:app)에 위 3가지 중 필요한 것을 선택해 추가한다.

ViewModel 클래스를 정의할 때 ViewModel을 상속하기 위해androidx.lifecycle.ViewModel을 import 해주어야하므로 1번은 필수이다.
ex) class MainViewModel : ViewModel() { ... }

viewModels 또는 activityViewModels(2번, 3번 방법)를 사용할 경우 각각 2, 3번 의존성을 추가해야한다.

1. ViewModelProvider 사용

class MainActivity : AppCompatActivity() {

private lateinit var mainViewModel : MainViewModel

override fun onCreate(savedInstanceState: Bundle?) {
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
...
}
}

ViewModelProvider의 첫 인자는 ViewModelStoreOwner를 의미한다. 쉽게 말하자면 ViewModel 저장소를 누가 소유하고 있냐는 의미이다. ComponentActivity, Fragment 등이 해당된다.

ViewModelProvider.kt에 정의된 생성자

ViewModelStore은 ViewModel들은 HashMap 형태로 저장하고 있기에 MainViewModel::class.java 를 Key값으로 사용해 ViewModel 객체를 가져온다. 이때 Key에 해당하는 Value가 없으면 생성하고 가져오므로 처음 ViewModel 객체를 만들더라도 get을 통해 가져올 수 있다.

2. by viewModels() 사용

class MainActivity : AppCompatActivity() {

private val mainViewModel by viewModels<MainViewModel>()

}

확장함수 ComponentActivity.viewModels()를 통해 MainViewModel 인스턴스를 생성하고 이를 mainViewModel 변수에 위임한다.

viewModels는 뭘까? 단순히 사용법만 알고 싶다면 밑에 나오는 내용은 넘어가도 좋다.

androidx.activity 패키지의 ActivityViewModelLazy.kt와 ViewModelLazy.kt 내부 코드

내부를 조금 들여다 보면 ViewModelLazy 클래스를 사용하고 있고 캐싱된 ViewModel이 있다면 반환해주고 없다면 ViewModelProvider를 통해 새로 생성하여 반환해준다. 반환타입으로 코틀린의 Lazy<out T> 인터페이스를 사용한다.

따로 지연 초기화 처리를 해주었기에 위의 ViewModelProvider 방식에서 사용했던 lateinit var를 사용하지 않아도 된다.

그런데 위의 코드에서 by를 꼭 써주어야할까?

private val mainViewModel = viewModels<MainViewModel>().value

위와 같은 방법으로 ViewModelLazy 인스턴스를 가져와 .value를 통해 뽑아낼 수도 있지만 프로퍼티 위임 패턴(by) 을 사용하는 것이 일반적이다.

public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}
Lazy.kt 내부

by 키워드를 사용하면 getValue 메소드가 호출되고 Lazy의 value를 반환한다. 이는 T 타입의 읽기전용 프로퍼티이다.

코틀린에서 by lazy를 통해 지연 초기화하는 것을 본 적이 있을 것이다. by lazy 또한 내부적으로 Lazy<out T> 인터페이스를 사용하고 프로퍼티 위임의 일종이다.

3. by activityViewModels() 사용

2번 방법과 유사하게 Fragment.activityViewModels() 확장함수를 사용한다.

Fragment에서만 사용할 수 있고, Activity 단에서 사용하는 ViewModel을 가져온다. 이를 통해 Activity와 Fragment, Fragment들 사이에 ViewModel을 공유해서 사용할 수 있다.

내부적으로 Fragment가 속해 있는 Activity의 ViewModelStore로부터 ViewModel을 가져오므로 먼저 해당 Activity에 위의 2가지 방법 중 하나를 선택해 ViewModel을 선언해주어야한다. 그 후 Fragement에서 다음 코드를 통해 ViewModel을 가져올 수 있다.

class FirstFragment : Fragment() {

private val mainViewModel by activityViewModels<MainViewModel>()

}

사용 시 주의사항

Activity, Fragment의 Context를 참조하면 안 된다.

  • UI보다 생명주기가 길기 때문에 메모리 누수가 발생할 수 있다.
  • Application context는 AndroidViewModel 통해 사용가능하다.

Gradle에 의존성을 추가하다가 문제가 발생했다면 settings.gradle에 google()이 있는지 확인한다.

pluginManagement {
repositories {
google()
}
}
dependencyResolutionManagement {
repositories {
google()
}
}

프로젝트 생성 시 기본적으로 설정되어 있으나 없다면 추가해 Google의 Android 관련 Gradle 플러그인을 사용할 수 있도록 한다.

--

--