[번역] LiveData와 SnackBar, Navigation 및 기타 이벤트(SingleLiveEvent 케이스)

hongbeom
5 min readNov 4, 2020

--

본 글은 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로 유지되고 처음 화면으로 돌아갈 수 없다는 것입니다. 단계별로 살펴보면 다음과 같습니다.

  1. 사용자가 버튼을 클릭하면 Details Activity가 시작됩니다.
  2. 사용자가 뒤로가기를 누르고 list Activity로 돌아옵니다.
  3. Activity가 back stack에 있는 동안 비활성화된 후 옵저버가 다시 활성화됩니다.
  4. 값이 여전히 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 함수를 추가해주셨습니다.

읽어주셔서 감사합니다🙌

--

--

hongbeom
hongbeom

No responses yet