📌근본적인 컨셉
View와 비즈니스 로직을 분리시키기 위함
제일 중요한 것은 "관심사 분리"
MVC로 결합되어 있는 Activity/Fragment에서 명확하게 M.V.C로 나눠야 함
View(etName)
와 비즈니스 로직(etName.text.toString().isBlank())
이 섞여있음
private fun getMessageValidName(): String = getString(
if (etName.text.toString().isBlank()) {
SignUpErrorMessage.NAME
} else {
SignUpErrorMessage.PASS
}.message
)
View에 대한 액션(if, else if)
을 ViewModel로 넘김 : checkValidName()
ViewModel에서는 View에 바인딩하기 전에 데이터를 가공함
private fun getMessageValidName() (
viewModel.checkValidName(etName.text.toString())
)
private val _nameErrorMessage: MutableLiveData<SignUpErrorMessage> = MutableLiveData()
val nameErrorMessage: LiveData<SignUpErrorMessage> get() = _nameErrorMessage
...
//View에 Binding을 하기 이전에 가공하는 과정
fun checkValidName(text: String) {
_nameErrorMessage.value = if (text.toString().isBlank()) {
SignUpErrorMessage.NAME
} else {
SignUpErrorMessage.PASS
}
// if else
// 조건문과 그 안의 값
}
View에 바인딩함
tvNameError.text = it.message
private fun initViewModel() = with(viewModel) {
nameErrorMessage.observe(this@SignUpActivity) {
tvNameError.text = it.message
}
}
LiveData의 목적 : LifeCycle에 따라서 데이터를 Observing 시켜준다.
Activity의 onStart/onResume 시점에 Observing을 활성화한다는 의미
Destroy되면 Observing을 끊어줘서 앱의 안정성을 높여준다.
동기 코드 : 위에서부터 아래로 흐름
비동기 통신 : 잠시 옆으로 새는 것
AndroidViewModel : Android 앱 전체의 라이프사이클을 제어할 때 필요함, Android 애플리케이션 수준의 설정 관리, Lifecycle이 ViewModel보다 길기 때문에 뷰가 없을 때 바인딩을 시도할 수 있음/메모리 leak. Activity나 Fragment에서는 굳이 사용할 일이 없을 것
ViewModel : Activity/Fragment의 Lifecycle의 영향을 받음, 일반적인 UI 구성 요소의 데이터 관리 및 UI 상태 유지
private fun initViewModel() = with(viewModel) {
nameErrorMessage.observe(this@SignUpActivity) {
tvNameError.text = it.message
}
}
// 에러메세지 설정을 View에서 해야해요
...
// addTextChangedListener or setOnFocusChangeListener 에서 호출됨
private fun getMessageValidName() (
viewModel.checkValidName(etName.text.toString())
)
View가 ViewModel한테 액션을 전달해줄 수는 있지만 받는 데이터는 LivewData를 통해 Observing해서 받아야 함
private val _nameErrorMessage: MutableLiveData<SignUpErrorMessage> = MutableLiveData()
val nameErrorMessage: LiveData<SignUpErrorMessage> get() = _nameErrorMessage
...
fun checkValidName(text: String): SignUpErrorMessage { // X
return if (text.toString().isBlank()) {
SignUpErrorMessage.NAME
} else {
SignUpErrorMessage.PASS
}
}
fun checkValidName(text: String) {
_nameErrorMessage.value = if (text.toString().isBlank()) { // O
SignUpErrorMessage.NAME
} else {
SignUpErrorMessage.PASS
}
}
비동기 통신을 할 때는 return이 안 될 수도 있다.
postValue를 해줘야 함
네트워크 통신을 하고 난 후 UI 갱신을 하려면 postValue를 써야 한다.
Main Thread / background Thread가 있음
UI에 데이터를 반영하기 위해서는 UI Thread를 통해서 데이터를 반영해야한다.
쉽게 말해서 이때까지 짰던 코드는 다 UI Thread에서 운영된 코드
UI Thread에서 해야하는데 데이터 바인딩을 하려고 보니까 background Thread면 앱이 죽어버림.
그래서 우리는 background thread에 있는 친구를 UI thread로 올릴 수 있는 함수인 postvalue를 사용해야 함
postValue : background thread에서 UI의 데이터를 갱신하는 게 목적
내부적인 코드로는 setValue를 씀
네트워크 통신을 하고 나서 UI 갱신을 하려면 postValue를 해야 한다.
kotlin context를 만들어주는 라이브러리 추가
implementation("androidx.activity:activity-ktx:1.8.2")
Listener를 ViewModel 안에 넣으면 안됨
View에 대한 액션(Button click, textChange, swith on/off)이 발생하면 액션을 ViewModel에 넘겨줘야 한다.