본 글은 Jose Alcérreca님의 글을 한국어로 번역한 글입니다. 본문 링크 👇
View(Activity나 Fragment)가 ViewModel과 통신할 수 있는 편리한 방법은 LiveData를 사용하는 것입니다. View는 LiveData
의 변경 사항을 구독하여 이에 반응합니다. 이 작업은 화면에 계속 표시되어야하는 데이터에 적합합니다.
그러나 Snackbar
메시지, navigation
이벤트 또는 다이얼로그와 같은 일부 데이터는 한 번만 사용해야 합니다.
이 문제를 아키텍처 요소에 대한 라이브러리 또는 익스텐션으로 해결할 것이 아니라 설계 문제로 생각해보아야 합니다. 이벤트를 상태의 일부로 취급하는 것이 좋습니다. 이 글에서는 몇 가지 일반적인 오류와 권장하는 방식을 확인해볼 수 있습니다.
❌ 나쁜 사례 1. LiveData를 이벤트를 위해 사용하기
이 접근 방법은 SnackBar
메시지 또는 navigation
신호를 LiveData
객체 내부에 보관합니다. 원칙적으로 LiveData
객체가 이를 위해 사용할 수 있는 것처럼 보이지만 몇 가지 문제가 있습니다.
list/detail 앱에서 list의 ViewModel은 다음과 같습니다.
View에서는 다음 작업을 수행합니다.
이 방법의 문제는 _navigatieToDetail
의 값이 오랫동안 true
로 유지되고 처음 화면으로 돌아갈 수 없다는 것입니다. 단계별로 살펴보면 다음과 같습니다.
- 사용자가 버튼을 클릭하면
Details Activity
가 시작됩니다. - 사용자가 뒤로가기를 누르고
list Activity
로 돌아옵니다. - Activity가 back stack에 있는 동안 비활성화된 후 옵저버가 다시 활성화됩니다.
- 값이 여전히
true
이므로Details Activity
가 다시 잘못 시작됩니다.
해결 방법은 ViewModel에서 navigation
을 실행한 후 즉시 플래그를 false
로 설정하는 것입니다.
그러나 한 가지 중요한 점은 LiveData가 값을 보유하지만 모든 값을 방출한다고 보장하지는 않는다는 것입니다. 예를 들어 활성화 상태인 옵저버가 없을 때 값을 설정할 수 있으므로 새로운 옵저버가 값을 대체합니다. 또한 서로 다른 스레드에서 값을 설정하면 옵저버에서 한 번의 호출만 생성되는 상태가 될 수 있습니다.
하지만 이 접근법의 가장 큰 문제는 코드를 이해하기 어렵고 별로라는 것입니다. navigation
이벤트가 발생한 후 값을 재설정하려면 어떻게 해야할까요?
❌ 개선 방법: 2. LiveData를 이벤트를 위해 사용하고 옵저버에서 이벤트 값을 리셋하기
이 방법을 사용하면 View에서 이벤트를 이미 처리했고 재설정해야 함을 나타내는 방법을 추가할 수 있습니다.
사용법
옵저버를 약간 변경하면 다음과 같은 해결책이 나올 수 있습니다.
다음과 같이 ViewModel에 새로운 메소드를 추가합니다.
이슈
이 접근법의 문제는 일부 보일러플레이트 코드가 존재하고 오류가 발생하기 쉽다는 것입니다. 옵저버에서 ViewModel에 대한 함수 호출을 잊어버리기 쉽습니다.
✔️ 해결 : SingleLiveEvent 사용하기
특정 시나리오에서 작동하는 솔루션으로 예제에 대한 SingleLiveEvent
클래스가 만들어졌습니다. 업데이트가 한 번만 전송되는 LiveData
입니다.
사용법
이슈
SingleLiveEvent
의 문제는 단일 옵저버로 제한된다는 것입니다. 실수로 두 개 이상 추가하면 한 개만 호출되고 어떤 것이 추가되었는지에 대한 보장이 없습니다.
✔️ 추천 : Event로 감싸기
이 접근 방식에서는 이벤트가 처리되었는지 여부를 명시적으로 관리하여 실수를 줄입니다.
사용법
이 접근 방식의 장점은 getContentIfNotHandled()
또는 peekContent()
를 사용하여 의도를 지정하는 것입니다. 이 함수는 이벤트를 상태의 일부로 모델링합니다. 이제 이벤트는 사용되었거나 사용되지 않는 메시지일 뿐입니다.
추가
글쓴이가 제시한 Event.kt의 gist에 추가로 Adam Hurwitz님께서 Flow에서 사용할 수 있도록 extension으로 OnEachEvent
함수를 추가해주셨습니다.