한 프로젝트를 개발하며
Coroutine과 Flow를 활용한 리모트 통신을 구현하고 있었습니다.
그런데 똑같은 값을 다시 받으면 무시되는 현상이 발견되어 원하던 동작을 하지 않았습니다.
그래서 StateFlow
를 사용하여 원하던 동작을 하도록 수정했죠.
여기서 저는 StateFlow
와 SharedFlow
가 정확이 어떤 차이가 있는지 잘 몰랐죠.
누군가에게 설명할 수 있을 정도가 아니라는 것을 깨달았습니다.
그래서 이번에 StateFlow
와 SharedFlow
에 대하여 정리해 보고자 합니다!!
기존에는 상태를 관리하기 위해 Observer
패턴으로 구현된 LiveData
를 사용했습니다.
하지만 LiveData에는 단점이 있습니다.
위의 사진은 클린 아키텍쳐의 의존성 관계입니다.
Data Layer는 Remote, Local 통신을 담당하죠.
하지만 LiveData는 비동기 데이터 처리에 적합하게 설계되지 않았습니다!
Main Thread에서 관찰이 진행되기 때문이죠!
Domain Layer는 위 그림과 같이 의존성을 가지지 않는 독립적인 계층입니다.
그러니 의존성을 가지지 않은 순수 Kotlin, Jave 코드로 이루어지죠.
LiveData는 AAC 라이브러리로 해당하는 모듈을 추가해야하는 문제가 발생합니다.
LiveData는 이러한 단점이 존재하고 있었습니다.
그리고 Kotlin Coroutine
이 뜨기 시작하며 Flow
가 등장했습니다.
여기서 Flow
는 코틀린에서 사용할 수 있는 비동기 스트림입니다.
그리고 "Flow
가 LiveData
를 대체할 수 있지 않을까?" 라는 기대가 생겼죠.
하지만.. 역시 문제가 있죠..
Flow
는 콜드 스트림으로 연속적으로 들어오는 데이터를 처리할 수 없습니다. Flow
는 스스로 안드로이드 생명주기를 알지 못합니다.Flow
는 상태가 없어 현재 값을 할당 중인지 모릅니다.그래서 SharedFlow
와 StateFlow
가 등장하게 되었습니다!
안드로이드 공식 문서에 따른 정의를 보겠습니다.
StateFlow와 SharedFlow는 흐름에서 최적으로 상태 업데이트를 내보내고 여러 소비자에게 값을 내보낼 수 있는 Flow API입니다.
StateFlow는 현재 상태와 새로운 상태를 Collector에 내보내는 관찰 가능한 상태를 담는 흐름입니다.
Hot Stream
입니다.따라서 LiveData를 대체할 수 있습니다! 그리고 AAC DataBinding에도 StateFlow가 호환되죠!
class ViewModel() {
val text = StateFlow<String>
}
<TextView
android:id="@+id/textview"
android:text="@{vm.text}"/>
이제 예제를 통해서 StateFlow를 보도록 하죠.
대충 ViewModel에서 카드의 내용을 가져와 뷰에 갱신해야하는 상황이라고 가정한다면..
class ViewModel() {
private val _cardState = MutableStateFlow(CardState())
val cardState: StateFlow<CardState> = _cardState
fun getCardContents() {
viewModelScope.launch {
val cardContents: CardState = 결과를 가져오는 함수
_cardState.value = cardContents
}
}
}
viewModelScope 안에서 카드의 내용을 가져오는 함수를 실행시킵니다.
그 결과를 _cardState의 값에 넣죠.
그렇다면 Activity에서 이 값을 받습니다.
class Acitivty {
...
fun collectCardState() {
lifecycleScope.launchWhenStarted {
viewModel.cardState.collect { state ->
if (state.result != null) {
makeCard(state.card)
}
if (state.error.isNotBlank()) {
showError(state.error)
}
}
}
}
}
Activity에서 StateFlow의 값을 collect 해줍니다.
먼저 관계를 보자면
Flow
->SharedFLow
->StateFlow
로 상속 관계가 이루어집니다.
SharedFlow
는Flow
를 상속 받고StateFlow
는SharedFlow
를 상속 받습니다.
따라서 SharedFlow
는 StateFlow
의 일반화된 버전으로 볼 수 있습니다.
그러면 바로 예제를 봅시다.
class ViewModel : ViewModel() {
private val _cardState = MutableSharedFlow<CardState>(
replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val cardState = _cardState.asSharedFlow()
fun getCardInfo() {
viewModelScope.launch {
_cardState.emit(CardState())
}
}
}
위의 코드에서
replay
: 새로운 구독자들에게 이전 이벤트 방출 여부 (0 = 방출X, 1 = 방출)
extraBufferCapacity
: 추가 버퍼 생성 여부 (1 = 생성)
OnBufferOverflow
: 버퍼 초과시 처리 여부 (DROP_OLDEST = oldest 데이터 drop)
class Acitivty {
...
fun collectCardState() {
lifecycleScope.launchWhenStarted {
viewModel.cardState.collect { state ->
if (state.result != null) {
makeCard(state.card)
}
if (state.error.isNotBlank()) {
showError(state.error)
}
}
}
}
}
SharedFlow와 똑같이 값을 받을 수 있습니다.
오랜만에 글을 정리해 보았어요!
부족한 점이 있으면 댓글로 꼭 알려주세요!
SharedFlow, StateFlow 개념을 잘 익힌 후
상황에 맞게 자유자제로 사용할 수 있는 것이 중요한거 같아요!
저의 문제 상황은 StateFlow가 중복된 리소스를 방지하여 값이 오지 않는다는 것이었으니
SharedFlow로 변경하여 원하는 동작을 하도록 수정하였습니다!!