Flow 는 단일 수신자에게 스트림을 제공하는 일반적인 방식입니다.
그럼, 수신자가 여럿인 경우는 어떻게 하면 좋을까요? StateFlow 와 SharedFlow 는 이러한 상황에 대한 좋은 선택지가 될 수 있습니다.
🤔 얘넨 또 뭐하는 애들인가요?
StateFlow 와 SharedFlow 는 상태의 업데이트를 최적화하고, 여러 소비자에게 값을 보낼 수 있도록 하는 Flow API 입니다. 여러 상황에 다이나믹하게 도입할 수 있어, 유연한 스트림 전송을 도와줍니다.
StateFlow
수집자에게 현재의 상태와 새로운 상태 업데이트를 방출하는 State-Holder Observable Flow 입니다.
State-Holder 라는 것은 상태를 보관할 수 있음을 의미합니다. 시간의 흐름에 따라 달라질 수 있는 특정 상태에 따라 프로그램의 행위를 정의할 수 있습니다.
Observable 은 LiveData 덕에 익숙할 겁니다. 상태의 변화를 Observing 할 수 있고, 이를 통해 상태 변화에 따라 특정 작업을 수행하면 되겠습니다.
선언법은 다음과 같습니다.
class FlowClass() {
private val _testFlow = MutableStateFlow(5) // 초기값 5
val testFlow: StateFlow<Int>
get() = _testFlow
}
현재 상태값은 value 프로퍼티로 참조할 수 있습니다.
직접 접근하는 것 같지만, getter 를 두었기 때문에 직접 접근하는 건 아닙니다.
fun doSomething() {
val currentState = testFlow.value
}
다음과 같은 방식으로도 구현이 가능하겠죠?
fun doSomething() {
when(testFlow.value) {
1 -> {
// show some dialog
}
2 -> {
// intent to other activity
}
3 -> {
// show snackbar
}
else -> {
// quit the app
}
}
}
위는 고정된 프로퍼티에 대한 참조이며, 다음 예제는 연속적으로 들어오는 값에 대한 Observing 입니다.
fun doSomething() {
CoroutineScope(Dispatchers.IO).launch {
viewModel.stateFlow
.onEach {
println(it)
}.collect()
}
}
위와 같이 구현하면 매번 들어오는 값에 대해 프린트할 수 있습니다.
StateFlow 는 상태의 변경이 있고, 이를 관찰할 수 있어야 할 때 적합합니다.
HotStream
StateFlow 는 일반 Flow 와 다르게 HotStream 입니다. Flow 에서 수집하려 해도 생산자 코드가 트리거되지 않습니다. 계속해서 흘러나옵니다. 즉, 메모리에 계속해서 탑재되어 있고, 참조가 없는 경우에만 메모리에서 해제됩니다.
새로운 소비자가 해당 StateFlow 에 대한 수집을 실행하면 현재 상태를 먼저 반환하고, 그 이후로 방출되는 값을 지속적으로 전달받게 됩니다. LiveData 와 똑같습니다.
stateIn()
Collections 와 Sequence 에 asFlow() 메서드를 통해 flow 로 바꾸었듯이, 모든 flow 는 stateIn() 메서드를 통해
StateFlow 로 바꾸어 줄 수 있습니다.
StateFlow 는 LiveData 와 닮아 있습니다. 둘 다 관찰 가능한 Data-Holder 입니다. 앱 아키텍쳐에 적용되는 모습도 비슷합니다. 그러니, LiveData 를 사용하듯 사용하면 될 것 같습니다. 물론 정답은 없습니다.
비슷한 두 클래스의 차이점은 다음과 같습니다.
LiveData 는 초기값이 없어도 되지만, StateFlow 는 꼭 초기값이 있어야합니다.
LiveData 는 앱 컨테이너의 수명주기를 따르지만, StateFlow 는 아닙니다.
만약 수명주기를 따르게 하고 싶다면 (수명주기에 따라 알아서 생성 및 해제 되도록 하고 싶다면) Lifecycle.repeatOnLifeCycle 블록에서 Flow 를 수집하면 되겠습니다.
SharedFlow
SharedFlow 는 여러 소비자에게 스트림을 보내기 위해 사용될 수 있습니다. StateFlow 와의 차이점은, 캐시와 버퍼를 가지고 있어, 뒤늦게 수집을 시작한 소비자에게도 전체 값을 보낼 수 있다는 점입니다.
선언법은 다음과 같습니다.
class FlowClass() {
private val _testFlow = MutableSharedFlow<Int>()
val testFlow: SharedFlow<Int>
get() = _testFlow
}
SharedFlow 의 프로퍼티
SharedFlow 의 프로퍼티는 다음과 같습니다.
private val replay: Int,
private val bufferCapacity: Int,
private val onBufferOverflow: BufferOverflow
replay 는 replayCache 의 capacity 입니다. 가령, 이를 10으로 설정하면 과거 10개의 값을 캐시에 저장해둘 수 있습니다. replayCache 는 관련 함수로 캐시 내에 무엇이 있는지 확인할 수 있고, 비울 수도 있습니다.
기본값은 0입니다.
bufferCapacity 역시 단순히 버퍼의 용량이며, 10으로 설정하면 필요시 10개의 값을 버퍼에 할당할 수 있습니다.
onBufferOverflow 는 BufferOverflow 발생시 대응 전략을 설정할 수 있습니다.
기본적으론 SUSPEND 이며, DROP_OLDEST, DROP_LATEST 가 있습니다.
shareIn()
stateIn() 과 같이 shareIn() 메서드를 사용하면 flow 를 SharedFlow 로 바꾸어줄 수 있습니다.
HotStream
SharedFlow 역시 HotStream 입니다. 계속해서 흘러나옵니다. StateFlow 와 똑같이 메모리에 계속해서 탑재되어 있고, 참조가 없는 경우에만 메모리에서 해제됩니다.
🤔 StateFlow 와 SharedFlow 를 적재적소에 사용하는 법?
간단합니다.
업데이트되는 특정값이 지속적으로 필요하다 -> StateFlow
받아온 값들에 대한 일시적 저장 및 과거의 값에 접근할 필요가 있다 -> SharedFLow
'Kotlin' 카테고리의 다른 글
ChannelFlow, CallbackFlow 제대로 알기 (0) | 2022.11.03 |
---|---|
Channel 제대로 알기 (0) | 2022.11.02 |
Sealed Class, Enum 제대로 알기 (0) | 2022.10.28 |
out, in 제대로 알기 (0) | 2022.10.26 |
코틀린의 5가지 범위 지정 함수 예 (Scope function) (0) | 2022.03.12 |