ViewModel을 구현하는 법은 많은 포스팅에서 봤으니까... 안전하게 사용하는 법을 알아보자!
ViewModel은 UI에 필요한 모든 데이터를 보유하고 처리한다. 그래서 뷰 계층 구조에 액세스하거나 Activity나 Fragment의 참조를 보유해서는 안 된다.
var
은 값을 할당할 때 setter, 속성 값을 읽을 때 getter를 호출한다.val
은 read-only기 때문에 기본적으로 getter만 생성된다.Kotlin에서 이렇게 by
를 활용해 속성 위임을 사용하면 getter-setter의 역할을 다른 클래스에 넘길 수 있다. 이때 다른 클래스를 바로 대리자 클래스(delegate-class)라고 한다. 아래와 같이 이용할 수 있다.
// Syntax for property delegation
var <property-name> : <property-type> by <delegate-class>()
왜 이렇게 속성을 위임 받아서 초기화 해야할까? viewModel로 계속 알아보자.
private val viewModel = GameViewModel()
위 코드처럼 기본 생성자를 사용해 초기화하면 ViewModel의 참조 상태를 읽게 된다. 기기를 회전해 Activity가 소멸되었을 때 ViewModel도 초기 상태의 인스턴스가 시작된다는 거다.
private val viewModel: GameViewModel by viewModels()
그러나 속성 위임 접근 방식으로 ViewModel 객체의 책임을 viewModels()라는 별도 클래스에 위임을 하게 되면, 이제 이 객체는 대리자 클래스인 viewModels()에 의해 내부적으로 처리된다. 이러면 첫 번째 액세스 시 자동으로 viewModel 객체를 만들게 되고, 구성이 변경되어도 계속 값을 유지했다가 요청이 들어올 때 반환하게 된다.
cf) Fragment 간 데이터 전달할 수 있는 공유 ViewModel
private val sharedViewModel: OrderViewModel by activityViewModels()
activityViewModels()는 fragment-ktx 모듈에 포함되어 있기 때문에 다음 의존성을 추가한다.
implementation "androidx.fragment:fragment-ktx:1.3.6"
cf) ViewModel의 생명주기
잊지 말자. ViewModel의 데이터를 다른 클래스에서 수정할 수 없도록 해야한다. 그러려면 데이터를 public으로 노출하지 말아야 한다.
privat var
이어야 한다.public val
이어야 한다.내부에서 활용하는 데이터 | 외부에서 활용하는 데이터 |
---|---|
private | public |
var | val |
그래서 지원 속성(backing property)를 이용한다. 이렇게 하면 앱 데이터가 외부 클래스로부터 원치 않게 변경되지 않게 보호할 수 있다.
// ViewModel 클래스 내에서만 액세스 가능, 수정 가능
// 이름 앞에 _을 붙여서 표기하자
private var _count = 0
// UI 컨트롤러(Activity나 Fragment) 같은 다른 클래스에서 액세스 가능
// get() 메서드만 있기 때문에 재정의 불가, read-only
val count: Int
get() = _count
아래와 같이 활용한다.
// GameFragment.kt
private fun updateNextWordOnScreen() {
binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord
}
// GameViewModel.kt
class GameViewModel : ViewModel() {
...
private lateinit var _currentScrambledWord: String
val currentScrambledWord: String get() = _currentScrambledWord
init {
_currentScrambledWord = "test"
}
}