LiveData는 관찰 가능한 데이터 홀더 클래스로 Activity 나 Fragment 등 앱 구성요소의 수명주기를 인식하여 동작한다.
저는 비동기적인 작업(Corouintes + Flow)을 수행 시 주로 사용하며 완료 상태에 따라 UI의 데이터 업데이트에서 Activity 나 Fragment 또는 two-way binding을 통한 xml에서 직접 LiveData를 통한 Observing을 적용하고 있습니다.
LiveData 특징
- UI(Activity, Fragment)와 데이터(ViewModel)의 상태 일치를 보장
Observer 패턴을 따르 데이터라 변경될 때마다 관찰자에게 알림 - LifeCycle를 통해 결합하므로 메모리 누수가 없음
- 구성 변경 시 최신 데이터를 받아옴
LiveData 사용법
Android Architecture Components 가이드에 따라 LiveData는 ViewModel에서 사용한다.
(ViewModel : 앱의 LifeCycle를 고려하여 UI관련 데이터를 저장 및 관리하는 컴포넌트)
@HiltViewModel
class BookDetailViewModel @Inject constructor(
private val bookRepository: BookRepository
) : ViewModel() {
// 1번
private val _isBookSaved = MutableLiveData<Int>()
// 2번
val isBookSaved: LiveData<Int>
get() = _isBookSaved
fun addBook(type: @BookType Int, bookInfo: BookInfo) {
val book = bookInfo.asBook()
book.type = type
viewModelScope.launch(Dispatchers.Main) {
if (!bookRepository.isSameBookSaved(bookInfo.title)) {
bookRepository.addBook(book)
// 3번
_isBookSaved.value = type
} else {
// 3번
_isBookSaved.value = BOOK_TYPE_UNKNOWN
}
}
}
}
위 코드는 데이터베이스에서 같은 책이 저장되어 있는지 조회(비동기작업) 한 뒤 LiveData를 통해 처리하는 코드이다.
1번에서는 Mutable형태의 LiveData를 선언하여 값을 get/set 할 수 있도록 한다.
2번에서는 읽기 전용의 LiveData를 선언하여 UI에서 참조할 수 있도록 하자. (물론 getter를 선언하여 1번의 값이 변경되면 인식할 수 있도록 한다)
3번에서는 비동기작업이 완료된 후 해당 LiveData의 값을 변경한다.
이제는 Fragment에서 어떻게 해당 LiveData를 Observe 하는지 살펴보자.
// BookDetailFragment.kt
// 4번
bookDetailViewModel.isBookSaved.observe(viewLifecycleOwner) {
when (it) {
BOOK_TYPE_UNKNOWN -> Toast.makeText(
requireContext(),
"독서내역에 추가된 책 입니다!",
Toast.LENGTH_SHORT
).show()
BOOK_TYPE_WISH -> {
findNavController().navigate(
BookDetailFragmentDirections.actionBookDetailFragmentToHistoryFragment(
bookType = BOOK_TYPE_WISH
)
)
}
}
}
4번에서는 ViewModel에서 선언한 LiveData를 구독하고 있는것을 알 수 있다.
방법은 간단하다. 해당 Fragment의 viewLifecycleOwner를 추가하면 된다.
나머지는 LiveData가 내부적으로 수명주기를 고려하여 변경된 데이터를 전달해 줄 것이다.
Fragment는 두 개의 LifeCycle이 존재한다.
하나는 Fragment의 LifeCycle과 다른 하나는 Fragment View의 Lifecycle이다.
그렇다면 왜 4번에서는 Fragment View의 Lifecycle인 viewLifecycleOwner을 사용하는가?
그 이유는 Fragment의 생명주기와 관련이 있는데,
예를 들어 구성 변경 시 Fragment는 Activity와 다르게 onDestory()를 호출하지 않고 onCreateView()를 호출한다.
이런 현상이 반복되다 보면 LivaData의 Observer가 여러번 호출되는 것을 알 수 있다.
이를 방지하고자 LiveData 사용 시 viewLifecycleOwner를 강제화하고 있는 것이다.
Thread 주의
위의 코드 중 3번을 보면 Main Thread에서 코루틴을 실행하고 set 방식으로 LiveData의 값을 변경함을 알 수 있다.
그렇다. LiveData는 항상 MainThread의 값을 처리한다.
하지만 다른 Thread의 값 또한 처리할 수 있다.
방법은 postValue를 사용하는 것이다.
_isBookSaved.postValue(type)
postValue의 Java 내부 코드를 보면 결국 postToMainThread()를 호출하여 해당 값을 Main Thread에 전달하는 것을 알 수 있다.
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
하지만 Main Thread가 아닌 IO 나 Default Thread에서 LivaData를 setValue를 하게되면 아래와 같은 에러를 만날 것이니 주의하라.
참고
https://developer.android.com/topic/libraries/architecture/livedata
https://pluu.github.io/blog/android/2020/01/25/android-fragment-lifecycle/
'Android' 카테고리의 다른 글
[Android] Fastlane 적용하여 빌드 및 배포 자동화 (0) | 2022.05.24 |
---|---|
[Android] 다른 앱 사용기록 조회 (0) | 2022.05.08 |
[Android] Activities and Intents (0) | 2022.03.01 |
[Android] Logger 라이브러리 확장하여 로그 파일로 저장하기 (0) | 2022.02.27 |
[Android] 깔끔한 Log를 사용하여 생산성 향상하기 (0) | 2022.02.26 |