SharedFlow는 Hot Flow로, SharedFlow를 수집하는 다수의 구독자가 동시에 같은 데이터를 수신할 수 있고, 한번에 여러 값을 발행할 수 있다는 특징이 있다.
기존의 Flow는 Cold Flow이다. Cold Flow는 구독자가 값을 수집하기 시작할 때 데이터를 생성하며, 하나의 구독자만 존재한다. Flow를 collect를 할때마다 flow의 block이 새롭게 실행되며, 이전 구독과는 독립적이다. (Cd 플레이어를 생각해보면 재생 버튼을 누를 때 음악이 나온다.)
Hot Flow는 구독자가 값을 수집하는지 여부와 상관 없이 데이터를 생산한다. Flow를 collect 하면 그 당시 방출되는 값부터 수집한다. (라디오를 생각해보면 라디오는 계속 흘러나오고, 내가 주파수를 맞추면 그 때부터 들려오는 라디오를 청취할 수 있다.)
그럼 SharedFlow 사용 방법과 속성에 대해서 알아보자.
val sharedFlow = MutableSharedFlow<Int>(
replay = 1,
extraBufferCapacity = 3,
onBufferOverflow = BufferOverflow.SUSPEND
)
SharedFlow는 데이터 방출 시 내부 버퍼를 사용하여 데이터를 관리한다. 버퍼의 동작은 다음 세 가지 주요 옵션에 의해 결정된다.
val sharedFlow = MutableSharedFlow<Int>(replay = 2)
runBlocking {
sharedFlow.emit(1)
sharedFlow.emit(2)
sharedFlow.emit(3)
sharedFlow.collect { value ->
println("Collected: $value") // 2, 3 출력
}
}
replay = 2 설정으로 인해 구독자는 마지막 두 개의 데이터(2, 3)를 수신.
val sharedFlow = MutableSharedFlow<Int>(replay = 1, extraBufferCapacity = 1)
runBlocking {
val success1 = sharedFlow.tryEmit(1) // 성공
val success2 = sharedFlow.tryEmit(2) // 성공
val success3 = sharedFlow.tryEmit(3) // 실패
println("Emit results: $success1, $success2, $success3") // true, true, false
}
replay = 1 : 한 개의 데이터는 항상 재전송 가능.
extraBufferCapacity = 1 : 추가 공간 한 개 제공.
총 버퍼 용량 = replay + extraBufferCapacity = 2.
BufferOverflow.SUSPEND (기본값): 대기하여 버퍼에 공간이 생길 때까지 기다림.
BufferOverflow.DROP_OLDEST: 가장 오래된 데이터를 삭제하고 새 데이터를 저장.
BufferOverflow.DROP_LATEST: 가장 최근 데이터를 삭제하고 새 데이터를 저장하지 않음.
val sharedFlow = MutableSharedFlow<Int>(
replay = 1,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
runBlocking {
sharedFlow.emit(1) // replay에 저장
sharedFlow.emit(2) // extraBufferCapacity에 저장
sharedFlow.emit(3) // 오래된 데이터 삭제 후 저장
sharedFlow.collect { value ->
println("Collected: $value") // 2, 3 출력
}
}
처음 방출된 데이터(1)는 삭제되고, 나중 데이터(2, 3)가 유지된다.
SharedFlow에 데이터를 발행하는 방법을 알아보자.
fun emitFlow() {
_sharedFlow.tryEmit(1)
}
suspend fun emitFlow() {
_sharedFlow.emit(1)
}
두 함수의 차이는 suspend 유무이다.
tryEmit은 중단 함수가 아니며, 일반 함수에서 flow에 값을 방출할 수 있다.
하지만 이렇게만 인지하고 있으면 예상하지 못하는 상황이 발생할 수 있다.
public fun tryEmit(value: T): Boolean
tryEmit은 Boolean 값을 반환한다. 값이 방출 성공되면 true, 실패하면 false를 반환한다.
그러면 값이 방출 실패되는 경우는 무엇일까?
val sharedFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.SUSPEND
)
runBlocking {
val success = sharedFlow.tryEmit(1)
println("Try emit success: $success") // true (성공 시)
val failed = sharedFlow.tryEmit(2)
println("Try emit success: $failed") // false (버퍼가 가득 찬 경우)
}
Try emit success: true
Try emit success: false
두번째 tryEmit 함수의 값은 false를 반환한다.
현재 구독자가 없는 상태이기 때문에 값이 바로 사용되지 못하고 위에 설명한 extraBufferCapacity라는 SharedFlow의 버퍼 공간에 이미 값 1이 저장되어 있어, 값 2는 정상적으로 방출되지 못한 것이다.
emit() 함수를 사용할 때 주의점도 존재한다.
onBufferOverflow의 기본값은 BufferOverflow.SUSPEND이며, emit() 중단 함수를 호출할 때 버퍼가 가득 찬 경우 버퍼에 공간이 생길 때까지 대기하게 되어, 호출한 코루틴이 정지 상태로 유지되는 상황이 발생할 수 있다.