본문 바로가기

Dev/안드로이드

[Android] SharedFlow 사용 시, 늦게 생성된 View에서의 초기 값 문제

📝 문제 발생

 

 LiveData를 StateFlow와 SharedFlow로 리팩터링 하던 도중, 사소한 문제가 생겼다. 특정 SharedViewModel의 LiveData를 Observe 해서 UI에 반영하는 코드가 있었는데, 리팩터링 이후 초기 값을 제대로 불러오지 못하는 문제였다.

 해당 Fragment 탭을 눌러서 그 Fragment가 생성된 이후에, SharedFlow로 방출되는 event들은 제대로 반영이 되고 있으나, 초기값은 의도한 대로(가장 마지막 event를 이용해 초기화) 동작하지 않았다.

 

 그래서 해당 SharedFlow를 collect하는 블럭에 Log를 찍어서 확인해 봤는데, View의 초기화 시점에는 해당 블럭이 트리거되지 않고 있었다. 

 

 

 📌 다양한 비동기 스트림과 문제 원인

 

 결론부터 말하자면 SharedFlow가 기본적으로 현재 상태 값을 갖지 않는 스트림이기 때문에 발생하는 문제였다. 안드로이드에서는 비동기 데이터 스트림을 관리하기 위해서 LiveData, Flow, StateFlow, SharedFlow 등을 이용하고 있는데 이들은 몇 가지 차이점을 지니고 있다.

 

LiveData: Android 아키텍처 컴포넌트로, Android에 종속적이다. 생명주기를 인식하며, 내부적으로 가장 최근 값만을 유지한다. Observer가 없더라도 최근 값을 항상 지니고 있으며, 모든 Observer들은 해당 스트림을 공유하게 되므로, Hot Stream의 성격을 지닌다고 할 수 있다.

 

 Flow: Kotlin Coroutines의 일부로, Kotlin에 종속적이다. 기본적으로 Cold Stream이기 때문에, collector가 없다면 데이터를 생성하지 않는다. 각각의 collector는 collect 시점부터 독립적인 데이터 스트림을 제공받는다고 할 수 있다. 이 Flow를 포함한 이하의 다른 Flow들은 View의 생명주기를 인식하지 않기 때문에, flowWithLifeCycle 등을 통해 적절하게 수명주기를 관리해줘야 한다.

 

 StateFlow: 상태 관리를 위해 설계된 Flow로, collector 존재 여부와 관계 없이 항상 최신 상태 값을 지니고 있다. 따라서 Hot Stream이며, LiveData와 유사한 성격을 지닌다. 그래서 생성 시 초기 값이 필요하고, UI 상태 관리에 유리하다.

 

 SharedFlow: 복잡한 이벤트 처리나 방출을 위한 Flow로, 다수의 collector에게 이벤트를 동시에 방출할 수 있다. 하지만 기본적으로 최신 데이터를 저장해두진 않으며, 각 collector에 대해 collect 시점 그 이후에 발생하는 이벤트들을 방출하게 된다. 그래서 collect 시점에 가장 마지막에 발생했던 event를 가져와 알아서 UI에 반영하는 로직이 발생하지 않는 것이다. 이는 StateFlow와 SharedFlow가 각각 UI 상태와 이벤트 처리에 특화된 Flow임을 보여주는 특징이기도 하며, SharedFlow는 생성 시에 초기 값이 필요 없는 이유도 그 때문이다.

 

 

 프래그먼트1에서 이벤트 발생해 SharedViewModel의 이벤트 발생 트리거 메소드 호출 -> SharedViewModel의 이벤트 SharedFlow 업데이트 -> 프래그먼트2에서 이 이벤트를 collect해 이벤트 데이터 바탕으로 자신의 ViewModel의 특정 메소드 호출 -> ViewModel 내에서 프래그먼트2를 위한 UI StateFlow(초기 값은 빈 리스트) 업데이트 -> 프래그먼트2에서 해당 StateFlow를 collect해 UI에 반영

 

 이제 이런 식으로 로직이 작성돼있다 보니, 프래그먼트2가 생성되지 않은 상태에서 프래그먼트1에서 발생한 이벤트들이 프래그먼트2의 초기 UI State에 정상적으로 반영되지 않고 있었다.

 

 

✔️ 해결

 

 SharedFlow는 위에서 설명했듯이 복잡하게 이벤트를 처리할 수 있다. 위와 같은 경우에 대비해서, 발생하는 이벤트들을 원하는 수만큼 캐싱해 두었다가 새로운 collector 발생 시 이를 재방출해줄 수 있으며, 추가 버퍼 설정 및 해당 버퍼 관리 정책도 지정 가능하다.

 따라서 SharedFlow는 입맛에 따라 ColdStream처럼 기능하게 할 수도 있고, HotStream처럼 기능하게 할 수도 있다.

 

Android Developers ❘ StateFlow and SharedFlow

 

 나는 문제가 생긴 SharedFlow에 replay 값으로 1을 지정했고, 그제서야 내가 원하는 대로 가장 최신의 이벤트를 바탕으로 늦게 생성되는 Fragment View의 UI State를 올바르게 나타낼 수 있었다.

 

 사용법은 매우 간단한데, 나는 여분의 버퍼가 필요없고 최근 이벤트 하나만 replay 해주면 되는 상태였기 때문에, 아래와 같은 코드를 작성했다.

 

class SearchSharedViewModel: ViewModel() {
	...
  	private val _event = MutableSharedFlow<SearchSharedEvent>(replay = 1)
    	val event: SharedFlow<SearchSharedEvent> = _event.asSharedFlow()
   	...
}

 

 

이번 기회에 다양한 비동기 스트림에 대해 좀 더 자세하게 찾아보게 되었는데, 생각하지 못했던 부분들을 많이 알게 된 것 같다.

 

 

 

참고

StateFlow and SharedFlow  |  Kotlin  |  Android Developers

 

StateFlow 및 SharedFlow  |  Kotlin  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. StateFlow 및 SharedFlow 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. StateFlow와 SharedFlow는 흐름에서 최적

developer.android.com

Kotlin flows on Android  |  Android Developers

 

Android에서의 Kotlin 흐름  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Android에서의 Kotlin 흐름 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 코루틴에서 흐름은 단일 값만

developer.android.com