TIL🔥스파르타 | MVVM

hyihyi·2024년 1월 31일
0

TIL

목록 보기
45/70
post-thumbnail

📖 MVVM

MVVM패턴의 기본 : ViewModel에서 데이터를 가공하고 LiveData를 통해 View에게 알려줌

📌근본적인 컨셉

View와 비즈니스 로직을 분리시키기 위함

  • Model : 데이터 관리
  • View : 화면에 데이터를 표시
  • ViewModel : Model 데이터를 요청하고 UI(View)를 위한 데이터를 가공하는 역할

🤔 MVVM 패턴을 왜 쓰는지?

📝 흐름

  1. View가 사용자의 액션을 받음
  2. 들어온 액션은 ViewModel로 전달됨. ViewModel은 Model에게 데이터를 요청
  3. Model은 ViewModel에게 요청받은 데이터를 전달
  4. ViewModel은 응답받은 데이터를 가공하고 저장
  5. View는 ViewModel을 관찰하고 변경된 데이터를 화면에 나타냄

📖 MVC VS MVVM

📖 MVC ➡ MVVM로 패턴변경하기

제일 중요한 것은 "관심사 분리"
MVC로 결합되어 있는 Activity/Fragment에서 명확하게 M.V.C로 나눠야 함

📝 View와 비즈니스 로직 분리 순서

분리하기 전 코드

Activity

View(etName)와 비즈니스 로직(etName.text.toString().isBlank())이 섞여있음

private fun getMessageValidName(): String = getString(
	if (etName.text.toString().isBlank()) {
		SignUpErrorMessage.NAME
	} else {
		SignUpErrorMessage.PASS
	}.message
)

1. 비즈니스 로직을 ViewModel로 옮긴 후 데이터 가공(옮긴 로직)

View에 대한 액션(if, else if)을 ViewModel로 넘김 : checkValidName()
ViewModel에서는 View에 바인딩하기 전에 데이터를 가공함

Activity

private fun getMessageValidName() (
	viewModel.checkValidName(etName.text.toString())
)

2. 가공한 데이터를 LiveData로 넘김(setValue)

ViewModel

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
	// 조건문과 그 안의 값
}

3. Activity에서 LiveData를 observe 하고 있다가 바뀐 값을 전달받을 때 호출됨

View에 바인딩함
tvNameError.text = it.message

private fun initViewModel() = with(viewModel) {
	  nameErrorMessage.observe(this@SignUpActivity) {
	      tvNameError.text = it.message
	  }
}

📝 LiveData

LiveData의 목적 : LifeCycle에 따라서 데이터를 Observing 시켜준다.
Activity의 onStart/onResume 시점에 Observing을 활성화한다는 의미
Destroy되면 Observing을 끊어줘서 앱의 안정성을 높여준다.

동기 코드 : 위에서부터 아래로 흐름
비동기 통신 : 잠시 옆으로 새는 것

😰 잘못된 방법

AndroidViewModel : Android 앱 전체의 라이프사이클을 제어할 때 필요함, Android 애플리케이션 수준의 설정 관리, Lifecycle이 ViewModel보다 길기 때문에 뷰가 없을 때 바인딩을 시도할 수 있음/메모리 leak. Activity나 Fragment에서는 굳이 사용할 일이 없을 것
ViewModel : Activity/Fragment의 Lifecycle의 영향을 받음, 일반적인 UI 구성 요소의 데이터 관리 및 UI 상태 유지

Activity

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())
)

ViewModel

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를 해야 한다.

📖 ViewModel 생성하는 여러가지 방법

📝 많이 쓰는 방법

kotlin context를 만들어주는 라이브러리 추가

implementation("androidx.activity:activity-ktx:1.8.2")

ViewModel에 어떤 코드가 들어가야 하는지?

Listener를 ViewModel 안에 넣으면 안됨
View에 대한 액션(Button click, textChange, swith on/off)이 발생하면 액션을 ViewModel에 넘겨줘야 한다.

profile
내가 이해하기 쉽게 쓰는 블로그

0개의 댓글