이번 포스팅 공부 할 내용은 Flow 중에서도 StateFlow, SharedFlow를 공부해 보겠다.
만약 Flow의 개념을 모른다면 링크에서 보고 오는 것을 추천한다.
# StateFlow vs SharedFlow
둘 다 Hot Stream이지만 용도에 맞게 사용해야 더 좋은 퍼포먼스를 낼 수 있기에 각각의 차이점을 알고 구분해서 사용하는 것이 중요하다.
## StateFlow
### 초기값이 필수적
private val _mainState: MutableStateFlow<MainState> = MutableStateFlow(MainState.Loading)
val mainState = _mainState
보통 ViewModel에서 위와 같이 사용할 텐데 초기값을 필수적으로 적어줘야 한다.
만약 초기값을 안 주면 아래와 같은 오류 메시지가 나온다.
해당 메시지를 번역해 보면"주어진 초기값으로 MutableStateFlow를 만듭니다."라고 나오기 때문에 초기값은 필수이다.
위 사진은 MutableStateFlow의 내부 구현인데 value는 제네릭이기 때문에 그 값이 반드시 non-null일 필요는 없다.
하지만 명확성과 유지보수 측면에서 null 사용을 권장하지 하지 않는다.
### 항상 최신 값 하나만 유지
StateFlow는 항상 최신 값 하나만 유지한다.
아래 예제는 MutableStateFlow Value에 값을 넣어준 후 Value값을 출력해 보는 예제이다.
val exStateFlow: MutableStateFlow<Int> = MutableStateFlow(0)
println("초기 exStateFlow ${exStateFlow.value}")
exStateFlow.value = 1 // 이전 값 0 사라짐
println("1대입 exStateFlow ${exStateFlow.value}")
exStateFlow.value = 2 // 이전 값 1 사라짐
println("2대입 exStateFlow ${exStateFlow.value}")
exStateFlow.value = 3 // 이전 값 2 사라짐
println("3대입 exStateFlow ${exStateFlow.value}")
예제 출력에서 보다시피 Value에 값을 넣어주면 이전 값들은 사라지고 최신 값 하나만 유지하게 되는 특성을 볼 수 있다.
### 중복 방출 된 값을 무시한다.
StateFlow는 distinctUntilChanged 속성이 true를 기본적으로 설정되어 있기 때문에 중복 방출 된 값을 무시한다.
아래 예제에서는 MutableStateFlow Value에 중복값을 넣어준 후 Value값을 출력해 보는 예제이다.
val exStateFlow: MutableStateFlow<Int> = MutableStateFlow(0)
// StateFlow 구독
launch {
exStateFlow.collect { value ->
println("방출된 StateFlow 값: $value")
}
}
delay(100)
exStateFlow.value = 1
delay(100)
exStateFlow.value = 1 // 중복
delay(100)
exStateFlow.value = 2
delay(100)
exStateFlow.value = 2 // 중복
delay(100)
exStateFlow.value = 1 // 이전 값과 다름
delay(100)
println 결과가 어떤지 먼저 생각해 보고 아래 내려서 출력 결과를 한번 보면 좋을 거 같다.
위 예제의 출력은 이렇게 0,1,2,1 이 출력되는 걸 확인 함으로써 중복 방출 값을 무시하는 특성을 볼 수 있다.
● 물론 distinctUntilChanged 속성을 false로 비활성화해 주면 되긴 하는데 권장하지 않는다.
### StateFlow 그럼 언제 사용 하나?
- UI 상태 관리
// UI 상태의 경우
data class UiState(
val isLoading: Boolean = false,
val data: List<String> = emptyList(),
val error: String? = null
)
val uiState = MutableStateFlow(UiState()) // 초기 상태 필수
- 설정 값 관리
// 설정 값의 경우
val darkMode = MutableStateFlow(false) // 기본값 지정 가능
- 현재 상태가 중요한 경우
//토글 상태
class ToggleViewModel : ViewModel() {
private val _isExpanded = MutableStateFlow(false)
val isExpanded = _isExpanded.asStateFlow()
fun toggleExpand() {
_isExpanded.value = !_isExpanded.value
}
}
## SharedFlow
### 초기값은 옵션
SharedFlow는 StateFlow와 다르게 초기값이 필수가 아닌 옵션이다.
보통 ViewModel에서 아래와 같이 사용할 텐데 초기값이 없어도 동작한다.
private val _mainEvent: MutableSharedFlow<FlowEvent> = MutableSharedFlow()
val mainEvent: SharedFlow<FlowEvent> = _mainEvent
sealed class FlowEvent {
data class ShowSnackbar(val msg: String) : FlowEvent()
}
### 여러 값 유지 가능
ShardFlow는 replay 캐시를 통해 여러 값을 유지할 수 있다.
아래는 MutableSharedFlow의 내부 정의이다.
@Suppress("FunctionName", "UNCHECKED_CAST")
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
require(replay >= 0) { "replay cannot be negative, but was $replay" }
require(extraBufferCapacity >= 0) { "extraBufferCapacity cannot be negative, but was $extraBufferCapacity" }
require(replay > 0 || extraBufferCapacity > 0 || onBufferOverflow == BufferOverflow.SUSPEND) {
"replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy $onBufferOverflow"
}
val bufferCapacity0 = replay + extraBufferCapacity
val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0 // coerce to MAX_VALUE on overflow
return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}
매개변수들을 보면 replay라는 매개변수가 있다. 바로 저 변수를 활용해 여러 값을 유지할 수 있다.
기본으로는 0이 되어있으며 기존에 있는 값을 유지하지 않는다.(최신 값 유지)
아래 예제 코드를 통해 좀 더 살펴보자!
val exSharedFlow = MutableSharedFlow<Int>(replay = 2)
exSharedFlow.emit(1)
exSharedFlow.emit(2)
exSharedFlow.emit(3) // 1, 2 값이 유지됨
// replayCache를 사용해 캐시 값 가져오기
println("현재 캐시된 값들: ${exSharedFlow.replayCache}")
예제에서 보다시피 replay를 2로 설정해 주니 캐시 된 값이 2,3이 나온다.
만약 여기서 replay를 3으로 설정해 주면 결과가 어떨까?
그러면 당연히 1,2,3이 나오게 된다. 따라서 ShardFlow는 replay 캐시를 통해 여러 값을 유지할 수 있다.
### 중복 값도 모두 방출한다
SharedFlow는 StateFlow와 다르게 distinctUntilChanged 속성이 없다.
따라서 중복 값도 모두 방출한다.
물론 아래 코드처럼 Flow 연산자를 사용해서 SharedFlow에도 distinctUntilChanged 동작을 추가 할 수 있다.
// distinctUntilChanged 적용
launch {
sharedFlow
.distinctUntilChanged() // 연속된 중복 값 제거
.collect { value ->
println("중복 제거된 값: $value")
}
}
아래 예제를 통해 확인해 보자 SharedFlow에 중복값을 넣어준 후 값을 출력해 보는 예제이다.
val exSharedFlow: MutableSharedFlow<Int> = MutableSharedFlow()
// exSharedFlow 구독
launch {
exSharedFlow.collect { value ->
println("방출된 SharedFlow 값: $value")
}
}
delay(100)
exSharedFlow.emit(1)
delay(100)
exSharedFlow.emit(1) // 중복
delay(100)
exSharedFlow.emit(2)
delay(100)
exSharedFlow.emit(2) // 중복
delay(100)
exSharedFlow.emit(1) // 이전 값과 다름
delay(100)
StateFlow와 마찬가지로 println 결과가 어떤지 먼저 생각해 보고 아래 내려서 출력 결과를 한번 보면 좋을 거 같다.
출력에서 보다시피 중복값도 모두 방출이 된다.
### SharedFlow 그럼 언제 사용 하나?
- 일회성 이벤트 처리
private val _toastEvent = MutableSharedFlow<String>()
val toastEvent = _toastEvent.asSharedFlow()
fun saveData() {
viewModelScope.launch {
// 데이터 저장 후
_toastEvent.emit("저장되었습니다")
// 추가 처리 필요 없음
}
}
- 여러 곳에서 동시에 수신할 때
lass MessageService {
private val _newMessages = MutableSharedFlow<Message>()
val newMessages = _newMessages.asSharedFlow()
suspend fun broadcastMessage(message: Message) {
_newMessages.emit(message)
}
}
// 여러 화면에서 동시에 구독
class ChatFragment : Fragment() {
// 채팅 화면에 메시지 표시
messageService.newMessages.collect { message ->
showMessage(message)
}
}
class NotificationWorker {
// 동시에 알림으로도 표시
messageService.newMessages.collect { message ->
showNotification(message)
}
}
# 총 정리
특성 | StateFlow | SharedFlow |
초기값 | 필수 | 선택적 |
최신값 유지 | 항상 유지 | replay 값에 따라 결정 (0: 유지X, 1이상: 유지) |
중복값 처리 | 동일한 값은 무시 | 모든 값 발행 |
주요 용도 | UI 상태 관리, 설정값 관리 | 일회성 이벤트, 실시간 데이터 스트림 |
# Flow vs StateFlow, SharedFlow
번외로 일반 Flow와 StateFlow, SharedFlow 이 두 가지의 큰 차이점을 간단하게 알아보자.
## Flow
- Cold Stream / 수집하는 Observer가 있을 때만 데이터를 방출
- 이전에 방출된 값들을 저장하지 않음
## StateFlow, SharedFlow
- Hot Stream / 수집하는 Observer가 없는 경우에도 데이터를 방출
- 최신 상태값을 항상 저장
'Kotlin 공부 노트' 카테고리의 다른 글
[Kotlin Flow] 코틀린 Flow란? - 예제와 함께 알아보기 (1) | 2024.09.17 |
---|---|
[Kotlin-Coroutine] coroutine Mutex(상호배제) 예제를 통한 사용법 (1) | 2024.06.09 |
[Android-Kotlin] Groovy DSL -> Kotlin DSL Migration(코틀린 DSL로 의존성 관리 마이그레이션), Kotlin DSL이란? (2) | 2023.10.29 |
DTO, DAO, VO에 알아보자! (0) | 2023.03.30 |
코틀린의 오브젝트란? (object, companion object) (0) | 2022.02.17 |