#1 개요
SharedFlow의 일종인 StateFlow에 대해 살펴본다.
#2 SharedFlow와의 비교
이름에서 알 수 있듯, StateFlow는 기존 SharedFlow에 State의 특성을 결합한 Flow다. 따라서 State.value로 State의 값을 참조했던 것과 같이, StateFlow.value로 StateFlow의 최신 emit값을 얻을 수 있다. 또 State를 선언할 때 초깃값을 할당해야하는 것처럼, StateFlow를 선언할 때도 그래야한다. StateFlow를 emit하면 해당 값이 자동으로 StateFlow.value에 들어가지만, 반대로 StateFlow.value에 명시적으로 값을 할당한다고 emit()이 수행되지는 않는다. 결과적으로, StateFlow.value는 초깃값 혹은 마지막으로 emit()된 값이라는 의미에 충실한다.
#3 예시 코드
#3-1 SharedFlow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
fun main() {
val sharedFlow = MutableSharedFlow<Int>()
// 리스너 1
CoroutineScope(Dispatchers.Default).launch {
sharedFlow.collect { value ->
println("(리스너 1) Collected value: $value")
}
}
runBlocking {
delay(1000)
sharedFlow.emit(1)
delay(1000)
sharedFlow.emit(2)
delay(1000)
sharedFlow.emit(3)
delay(1000)
}
// 리스너 2
CoroutineScope(Dispatchers.Default).launch {
sharedFlow.collect { value ->
println("(리스너 2) Collected value: $value")
}
}
runBlocking {
delay(1000)
sharedFlow.emit(4)
delay(1000)
sharedFlow.emit(5)
delay(1000)
}
}
/* ↑ ↑ ↑ 출력 결과
(리스너 1) Collected value: 1
(리스너 1) Collected value: 2
(리스너 1) Collected value: 3
(리스너 1) Collected value: 4
(리스너 2) Collected value: 4
(리스너 1) Collected value: 5
(리스너 2) Collected value: 5
*/
간단한 SharedFlow 코드다.
#3-2 StateFlow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
fun main() {
val stateFlow = MutableStateFlow(0)
// 리스너 1
CoroutineScope(Dispatchers.Default).launch {
stateFlow.collect { value ->
println("(리스너 1) Collected value: $value")
}
}
runBlocking {
delay(1000)
stateFlow.emit(1)
delay(1000)
stateFlow.emit(2)
delay(1000)
stateFlow.emit(3)
delay(1000)
}
// 리스너 2
CoroutineScope(Dispatchers.Default).launch {
stateFlow.collect { value ->
println("(리스너 2) Collected value: $value")
}
}
runBlocking {
delay(1000)
stateFlow.emit(4)
delay(1000)
stateFlow.emit(5)
delay(1000)
}
}
/* ↑ ↑ ↑ 출력 결과
(리스너 1) Collected value: 0
(리스너 1) Collected value: 1
(리스너 1) Collected value: 2
(리스너 1) Collected value: 3
(리스너 2) Collected value: 3
(리스너 1) Collected value: 4
(리스너 2) Collected value: 4
(리스너 1) Collected value: 5
(리스너 2) Collected value: 5
*/
#3-1의 출력 결과와 비교하면, "(리스너 1) Collected value: 0"와 "(리스너 2) Collected value: 3"이 추가로 존재한다. 이는 신규 리스너 등장 시의 동작 차이 때문이다 (#2 참조).
#4 존재 의의 (State가 가지지 못하는 StateFlow의 장점)
위 게시글의 #5에서 State가 LiveData보다 비교 우위에 있음에 대해 서술했다. 즉, 이미 State 만으로도 LiveData를 대체할만 하다. 하지만 구글 개발자가 작성한 이 게시글은 LiveData를 StateFlow로 마이그레이션하라고 권장하고 있다. 왜 StateFlow까지 사용하는가? 그 이유는 아래와 같다.
1. LiveData와 State 모두 비동기 데이터 스트림을 처리할 수 있게 설계되지 않았다 (따라서, 외부 서버 등에서 받아오는 데이터 스트림을 View까지 매끄럽게 전달하지 못함).
2. StateFlow는 동기 코드인 State와 달리 비동기적으로 작동한다. 따라서, 병렬 처리에 더 유연하다.
3. State는 사용할 수 없는, Flow의 중간 연산자(Intermediate operator)를 쓸 수 있다.
4. 백 프레셔 처리가 용이하다.
이어지는 글에서, LiveData가 쓰인 안드로이드 프로젝트를 StateFlow로 마이그레이션해본다 (본 게시글의 #6-2 참조).
#5 요약
StateFlow는 State + Flow로, 각각의 장점을 전부 활용한다.
#6 이어지는 글
#6-1 Jetpack Compose에서 StateFlow 사용하기
#6-2 기존 XML 구조에서 StateFlow 사용하기
LiveData가 사용된 전통적인 XML 구조의 프로젝트를 StateFlow로 마이그레이션해본다.
'깨알 개념 > Kotlin' 카테고리의 다른 글
[Kotlin] Coroutines Flow - Flow.combine()과 Flow.stateIn() (0) | 2024.08.29 |
---|---|
[Kotlin] Coroutines Flow - Intermediate operator (0) | 2024.08.16 |
[Kotlin] Coroutines Flow - Back pressure와 그 처리 (0) | 2024.08.15 |
[Kotlin] Coroutines Flow - 기초 (0) | 2024.08.01 |
[Kotlin] Coroutines - 한 Scope 내에서의 계층 관계 (0) | 2024.07.31 |