앱에서는 데이터의 변화가 끊임없이 일어납니다. 이때 마다 앱의 UI를 갱신시켜야 하는데 상당히 번거로운 작업입니다. 이때 LiveData를 사용한다면 UI를 자동으로 갱신해줄 수 있습니다. 자동으로 갱신되는 이유는 LiveData가 Observer Pattern을 사용하기 때문입니다.
Observer Pattern이란 관찰 대상(Observable)과 관찰자(Observer)로 구성된 디자인 패턴입니다. 보통 Observable에서 변화가 나타나면 Observer에서 이를 인식하여 특정 동작을 수행합니다
LiveData는 Observable에 해당합니다. 즉 관측의 대상입니다. 그렇다면 Observer는 누구일까요? 바로 UI를 업데이트 하는 함수입니다. LiveData를 UI 업데이트 함수가 관찰하고 있다가 변화를 감지하면 해당 변화에 맞게 UI 업데이트를 진행합니다.
LiveData 사용방법은 아래의 간단한 노트 앱을 통해 알아보겠습니다.
https://github.com/20Han/hilt_example
먼저 NoteViewModel을 살펴봅시다
class NoteViewModel(
private val repository: NoteRepository
): ViewModel() {
private val _note = MutableLiveData<Note>()
val note : LiveData<Note>
get() = _note
init {
viewModelScope.launch {
_note.value = repository.getNote()
}
}
}
LiveData가 보이는데 MutableLiveData, LiveData 이렇게 두 종류가 있습니다. 각각은 가지고 있는 value를 변화시킬 수 있는가에 따라 구분됩니다
MutableLiveData 하나만 사용하지 왜 LiveData를 getter로 두어 사용하는지 궁금하실겁니다. 그것은 바로 데이터의 수정, 삭제, 생성등의 처리가 viewModel안에서만 발생하도록 제한하기 위해서 입니다. 만약 MutableLiveData를 private으로 하지 않고 다른 클래스에 공개하면 어디서든 LiveData에 변화가 생길 수 있습니다. 따라서 value를 설정할 수 없는 getter를 둡니다.
LiveData를 만들었으니 어떻게 관찰하는지 살펴봅시다. 관찰은 해당 데이터를 UI에 그리는 activity혹은 fragment에서 이루어집니다.
class MainActivity : AppCompatActivity() {
private val noteViewModel : NoteViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
noteViewModel.note.observe(this, {
findViewById<EditText>(R.id.note_title).setText(it.title)
findViewById<EditText>(R.id.note_description).setText(it.description)
})
}
}
위 코드에서 주의깊게 봐야할 곳은 noteViewModel.note.observe 부분입니다. 이는 noteViewModel에서 getter로 설정한 note 변수를 관찰한다는 뜻입니다.
observe함수는 변수를 두 개 받습니다.
lifeCycleOwner
첫 번째 변수인 lifeCycleOwner는 Observer가 따르는 LifeCycle입니다. 위 코드에서는 this가 들어갔으니 MainActivity의 LifeCycle을 따릅니다. LifeCycle이 Started, onResumed 일때만 observer가 작동한다는 점이 중요합니다. 또한 onDestroyed에서는 observer가 해지됩니다.
Observer
UI 업데이트 함수를 가진 Observer입니다. 원래는 아래와 같이 코드를 써야하지만 kotlin semantic상 Observer는 생략할 수 있습니다.
noteViewModel.note.observe(this, Observer{
findViewById<EditText>(R.id.note_title).setText(it.title)
findViewById<EditText>(R.id.note_description).setText(it.description)
})
위와 같이 설정하면 viewModel의 LiveData값이 바뀔때마다 UI가 갱신됨을 알 수 있습니다.
LiveData에는 ArrayList 타입도 담을 수 있습니다. 그런데 ArrayList타입을 사용하다보면 Observer를 등록해도 UI가 갱신이 안되는 현상이 생깁니다. 아래와 같은 상황입니다
class NoteViewModel(
private val repository: NoteRepository
): ViewModel() {
private val _note = MutableLiveData<ArrayList<Note>>()
val note : LiveData<ArrayList<Note>>
get() = _note
init {
viewModelScope.launch {
_note.value = repository.getNotes()
}
}
fun addNote(note : Note){
_note.value?.add(note)
viewModelScope.launch {
repository.saveNote(note)
}
}
}
위 코드에서 addNote()를 아무리 호출해도 UI상에는 새로운 노트가 나타나지 않습니다. 분명 MutableLiveData의 값은 변했는데 말이지요.
이는 arrayList객체에 담긴 값만 변화했을 뿐 arrayList가 새로 할당된것이 아니기 때문입니다. 즉 LiveData는 값이 바뀌었다고 인식하지 않습니다. 따라서 Observer를 작동시키기 위해서는 addNote함수를 아래와 같이 LiveData를 재할당하는 형태로 수정해야 합니다.
fun addNote(note : Note){
_note.value = _note.value?.also{ it.add(note) }
viewModelScope.launch {
repository.saveNote(note)
}
}
위와 같이 _note.value를 재할당하면 Observer가 정상적으로 불림을 알 수 있습니다. 물론 원소하나를 추가만 하면 되는건데 굳이 value를 재할당해야하나 하는 의문이 듭니다. 혹시 더 좋은 방법을 알고 계신다면 댓글로 알려주세요!