MVVM을 이해하기 위해 알아보는 LiveData
추후 더 자세한 내용을 정리할 예정.

# LiveData란?

직역하면 살아있는 데이터? 이렇게 생각할 수 있다. LiveData는 LifeCycle을 알고 있는 DataType이라고 생각하면 좋다. 이처럼 LifeCycle을 알고 있으면 필요할 때 변경하고 필요하지 않을 때 변경하지 않을 수 있다.

또한, LiveData는 Observer 패턴을 따른다. 즉 데이터의 변경이 일어났을 때 콜백으로 받아 처리할 수 있다. 이렇게 데이터의 변경이 일어날때마다 콜백을 실행하는데 LifeCycle을 알고 있기 때문에 필요하지 않을 때는 콜백이 실행되지 않는다.

예를 들어 Activity에 선언되어 있는 LiveData의 경우 Activity가 Start, Resume 상태일 때는 콜백을 실행하지만 다른 액티비티로 넘어가 있는 onStop 등의 상태일 때는 실행되지 않는다.

  • postValue : 간단히 데이터가 변경된다.

이렇듯 onStart, onResume의 상태일 때 A와 B를 받는 옵저버 콜백은 실행되지만 onStop일 때 C와 D일 때는 실행되지 않고(옵저버 되지 않음) 다시 액티비티가 실행되면 가장 최신의 데이터인 D를 실행한다.(옵저버 됨) 이렇듯 LiveData를 사용하면 RxJava나 Interface Callback을 사용할 때 보다 더 깔끔하게 처리할 수 있다.

# 간단한 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
class UserProfileViewModel : ViewModel(){

// MutableLiveData란 변경할 수 있는 LiveData 형이다.
// 일반적인 LiveData형은 변경할 수 없고 오로지 데이터의 변경값만을 소비하는데 반해
// MutableLiveData는 데이터를 UI Thread와 Background Thread에서 선택적으로 바꿀 수 있다.
private val _post = MutableLiveData<User>()


// _post로 선언된 MutableLiveData를 post를 통해 발행한다.
// 이렇듯 ViewModel에서만 _post를 변경할 수 있기 때문에 보안에 더 좋다.
val post : LiveData<User>
get() = _post
}
  • 위와 같은 데이터를 Activity에서 받으려면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PostActivity() : AppCompatActivity(){

override fun onCreate(savedInstanceState : Bundle?){
...

// 위의 ViewModel에서 post LiveData를 Observe한다.
// 첫 번째 인자는 UI이며 해당 인자로 어떤 UI Thread를 사용할 지 결정한다.
// 두 번째는 Observe 콜백이다.
postViewModel.post.observe(this,
Oberserver{
post -> postTitle.text = post?.title
})
}
}

LiveData 콜백을 실행하는 방법은 아래처럼 두 가지가 있다.

1
2
3
4
// MutableLiveData에ㅓ setValue, postValue 실행 하는 경우

post.setValue(post) // UI Thread 즉, Main Thread에서 실행
post.postValue(post) // Background Thread에서 실행

사실 LiveData를 더 잘쓰려면 데이터바인딩과 함께 사용해야 좋은 효과를 낼 수 있다. 왜냐하면 위에서처럼 Observe 패턴을 이용해서 UI를 직접 변경해줄 필요 없이 xml 상에서 깔끔하게 처리할 수 있다.

1
2
3
<TextView
....
android:text="@{viewModel.post.title}/>

이처럼 아주 깔끔하게 선언할 수 있다 이제 title이 변경되는대로 TextView UI는 알아서 변경이 된다.

그렇다면 LiveData Observer UI(Activity, Fragment)가 사라진다면? 더 이상 새로운 데이터를 발행하지 않는다. rx로 따지면 Dispose가 필요없다. 즉, 데이터를 더 이상 발행하지 않기 때문에 알아서 구독을 해지하는 것으로 생각이 든다.

# LiveData 변형하기

간단하게 살펴보고 추후에 자세하게 살펴보도록 하겠다.

Map

  • LiveData의 변경을 다른 LiveData에게 알려주는 메소드.
1
2
3
4
val userLiveData : LiveData<User> = ...
val userNameLiveDat = Transformations.map(userLiveData, user -> {
return "${user.firstName}, ${user.lastName}" // String을 리턴한다.
})

UserLiveData의 변경사항을 Observe해서 map 함수를 통해서 원하는 값으로 변경한 뒤 String을 리턴한다. 즉, 새로운 LiveData를 리턴하는게 아니라 데이터만 변경한다.

SwitchMap

  • LiveData의 변경사항을 받아서 다른 LiveData를 발행한다. 일반적으로 RoomDatabase를 LiveData로 쓸 때 많이 사용된다고 한다.
1
2
3
4
5
6
7
8
val userIdLiveData : MutableLiveData = ...
val userLiveData : LiveData = Transformations.switchMap(userIdLiveData, id ->
repository.getUserById(id)) // LiveData를 리턴한다.


fun setUserId(userId : String){
this.userIdLiveData.setValue(userId)
}

SwitchMap은 데이터의 인자값에 따라 다른 LiveData를 발행한다. repository.getUserById(id)는 RooDatabase에서 ID 값에 따라 유저값을 가져오며 return 값이 LiveData이다. 즉, 인자 값에 따라서 다른 데이터 소스(LiveData)를 보낼 수 있다.

MediatorLiveData

  • 여러 데이터 소스를 한 곳에서 Observe할 때 사용한다.
1
2
3
4
5
6
val liveData1:LiveData = ...
val liveData2: LiveData = ...

val liveDataMerger:MediatorLiveData = new MediatorLiveData<>()
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value))
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value))

예를 들어 Fragment별로 LiveData가 있고 이걸 Activity 한 곳에서 Crashlytics에 기록한다거나 Toast 메시지를 띄울 때 Fragment의 LiveData를 Activity의 MediatorLiveData를 통해 사용할 수 있다.

LiveData는 많은 기능이 있는 것 같다. 하지만 혼자 쓰기 보다는 DataBinding, ViewModel과 함께 MVVM 패턴에서 사용될 때 효과가 더욱 두드러질 것 같다는 생각이 든다.

LiveData와 관련된 자세한 내용을 추후에 공부해서 올릴 예정이다.
앞으로도 열심히하길~

# 참고