StateFlow & SharedFlow
StateFlow 와 SharedFlow 는 다수의 소비자에게 상태 및 값을 방출하는게 가능하도록 최적화된 Flow 이다.
Flow 와 StateFlow & SharedFlow 에 cold flow, hot flow 라는 얘기가 자주 나오는데 무슨 뜻일까?
Cold & Hot Flow
그냥 Flow 는 cold Flow 이다. 소비자가 활성화되어 있지 않으면 방출도 중지된다. 소비자와 생산자가 1 대 1 관계이다.
StateFlow 와 SharedFlow 는 Hot Flow 이다. 소비자랑 상관 없이 참조가 존재하는 한 메모리에 남아서 방출한다. 다수의 소비자가 가능하다.
StateFlow
state-holder 로 observable flow 이다. 현재 상태 또는 새로운 상태 업데이트를 collectors 한테 방출한다.
value property 로 현재 상태를 read 할 수 있다. 새로운 상태로 변경하기 위해서는 MutableStateFlow 의 value property 로 변경해야 한다.
이는 기존 LiveData, MutableLiveData 와 아주 비슷한 형태를 띄고 있다.
stateFlow 는 android 에서 observable mutable 상태를 유지해야하는 클래스에 아주 적합하다!
View 가 UI 상태 업데이트를 listen 하고 구성 변경에도 현재 상태를 그대로 가져가서 상태가 지속되도록 할 수 있다.
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// 백킹 프로퍼티 사용
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
// flow collect 해서 값 업데이트 collect 할 때 마다 변경됨
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
sealed class LatestNewsUiState {
data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
data class Error(val exception: Throwable): LatestNewsUiState()
}
MutableStateFlow 를 업데이트 하는 클래스가 생산자이며 StateFlow 로 부터 값을 수집하는 모든 클래스가 소비자이다.
flow 빌더를 사용하여 빌드한 cold flow 와 다르게 hot 인 StateFlow 는 collect 해도 생산자 코드가 trigger 되지 않는다. hot & cold Flow 에서 설명했듯이 StateFlow 는 메모리에 항상 활성화되어 방출될 수 있으며 아무 참조가 없을 때 GC 당한다.
새로운 소비자가 collecting 를 시작하면 마지막 stream 에서 최신 state 를 받아온다.
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// LiveData 와 달리 안드로이드 생명주기에 따라 작동하지 않는다.
// repeatOnLifecycle 을 사용
// STOPPED 면 collect 중지 STARTED 가 되면 재개한다.
repeatOnLifecycle(Lifecycle.State.STARTED) {
latestNewsViewModel.uiState.collect { uiState ->
// 새로운 상태를 collect 했을 때
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
}
경고 : UI Update 가 필요할 때 flow 의 launch or launchIn 을 곧바로 사용해선 안된다. 이 확장 함수는 View 가 보이지 않을 때도 호출되어 app crash 로 이어질 수 있다. 따라서 RepeatOnLifeCycle 을 사용해라.
flow 를 stateFlow 로 변경하고 싶으면 stateIn 을 사용하며 된다!
LiveData vs StateFlow
- StateFlow 는 LiveData 와 다르게 initial state 를 필수로 가져야 하고 따라서 생성자로 넣어줘야 한다.
- View 가 STOPPED 상태가 되면 LiveData.observe() 는 소비자의 등록을 자동으로 취소한다. 그러나 StateFlow 는 자동으로 중지하지 않아 repeatOnLifecycle 블록에서 flow 를 수집해야 한다.
참고: shareIn 을 사용하면 cold flow 를 hot flow 로 만들 수도 있다. shareIn 은 SharedFlow 를 반환한다.
SharedFlow
안드로이드 개발할 때 Event를 어떻게 처리할지 고민해본 경험이 있을 것이다. 필자는 Event wrapper 클래스를 사용한 커스텀 SingleLiveData 를 만들어 사용했다.
SharedFlow 를 사용하면 보다 간편하게 이벤트를 처리할 수 있다. 다음의 특징을 가지기 때문이다.
StateFlow 와 다르게 Event 는 기본값이 없고 가장 최신의 State 를 받아와서도 안된다. 또한 여러 번 같은 이벤트가 발생해도 계속 발생해야한다.
SharedFlow 특징
- 소비자의 구독 이후 발생한 이벤트만 전달해준다.
- 중복된 값이 방출되어도 모두 collect 한다.
StateFlow 는 새로운 상태가 입력된 경우에만 소비자가 수집할 수 있다. 하지만 SharedFlow 의 경우 중복된 이벤트가 발생해도 모두 collect 한다.
private val _event = MutableSharedFlow<SomeEvent>()
val event = _event.asSharedFlow()
// 이벤트 처리
someEvent.collect { event ->
when(event) {
is ... -> {}
is ... -> {}
}
}
sealed class SomeEvent { ... }
위와 같이 간단하게 이벤트를 처리할 수 있다.
SharedFlow 는 3가지 인자를 가진다.
- replay : 새로운 구독자에게 이전 이벤트를 몇 개까지 전달할 것인가. default 0 이다.
- extraBufferCapacity : 버퍼를 생성해서 emit 한 데이터가 버퍼에 유지
- onBufferOverflow : 버퍼가 가득찼을 때 어떻게 처리할 것인지
이제 LiveData 를 Stateflow & SharedFlow 로 리팩터링 해야겠다! 끗
공식문서
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
참고한 블로그
https://myungpyo.medium.com/stateflow-와-sharedflow-32fdb49f9a32
https://medium.com/prnd/mvvm의-viewmodel에서-이벤트를-처리하는-방법-6가지-31bb183a88ce
'Android' 카테고리의 다른 글
Reflection [우아한테크코스 5기 AN_베르] (0) | 2023.09.10 |
---|---|
[안드로이드] 테스트 라이브러리 Robolectric (0) | 2023.09.03 |
[안드로이드] Flow 공식문서로 이해하기 - 1차 Flow 기초 (0) | 2023.08.25 |
[안드로이드] ViewModel 테스트하기 : [우아한테크코스 5기 AN_베르] (0) | 2023.07.22 |
[안드로이드] Fragment Lifecycle (프래그먼트 생명 주기) (0) | 2023.05.30 |