[Android A..Z] Flow StateFlow vs SharedFlow 비교

2024. 3. 30. 02:15개발/[Kotlin] 안드로이드 개발

반응형

개요

프로젝트에서 StateFlow와 SharedFlow를 사용하면서 차이점을 블로그에 정리합니다.

StateFlow

StateFlow는 상태 플로우라는 뜻입니다.

문자 그대로 현재 상태를 표현하기 적합한 flow입니다.

StateFlow는 초기값이 필요합니다. 따라서 생성자에 반드시 초기값을 명시해야하며 null에 대한 위험성이 없습니다.

 

StateFlow에 값을 전달할 때는 flow의 기본 함수인 emit()을 사용해도 되고 value 속성을 사용할 수도 있습니다.

private val _stateFlow = MutableStateFlow(99) //초기값을 99로 설정
val stateFlow = _stateFlow

_stateFlow.value = 1
_stateFlow.emit(1)

 

private val _stateFlow = MutableStateFlow(99)
    val stateFlow = _stateFlow

    suspend fun startStateFlow() {
        repeat(10) {
            _stateFlow.value = it
            delay(100L)
        }
    }

 

viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                optionViewModel.startStateFlow()
            }
        }
        
viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                optionViewModel.stateFlow.collectLatest {
                    println("!!!!!방출된 값 $it")
                }
            }
        }

위와 같이 코드를 동작시킬 때 하나의 루틴에서 startStateFlow() 함수를 통해 값을 방출하고 0~9의 값을 수집합니다.

 

이전 포스팅에서도 보면 알 수 있듯 StateFlow는 hotStream으로 이미 데이터가 방출되고 있으면 collect한 시점부터 값을 수집합니다.

정확히 HotSteram인지 확인하기 위해 다음과 같이 코드를 고쳐보겠습니다.

viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                optionViewModel.startStateFlow()
            }
        }
        
viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                delay(400L)
                optionViewModel.stateFlow.collectLatest {
                    println("!!!!!방출된 값 $it")
                }
            }
        }

startStateFlow() 함수로 인해 이미 데이터가 방출이 되고 있고 400L 이후에 수집하기 때문에 3부터 수집하는걸 볼 수 있습니다.

즉 현재 collect가 호출되면 현재 데이터부터 수신되기 시작하며, 여러 곳에서 collect를 호출하면 collect를 시작한 시점에서 각각의 데이터를 전달받기 시작합니다

SharedFlow

SharedFlow는 공유되는 플로우라는 뜻입니다.

공유란? Collector가 여러개인 경우,

Collector 들이 emit된 값들을 동시에 consume 할 수 있도록 공유되는 Flow API입니다.

 

Hot Stream과 Buffer, replay

만약 Collector가 준비가 안되어서 데이터를 받지 못하는 것을 방지하려면 어떻게 할까요?

SharedFlow에는 Buffer가 있어서 데이터를 받지 못하는 것을 방지할 수 있습니다.

 

Buffer란? 여러 가지 데이터를 보유하고 있는 메모리 영역을 가리킵니다.

 

private val _sharedFlow = MutableSharedFlow<Int>(
        replay = 0,
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
val sharedFlow = _sharedFlow

 

replay란? 

collect시 전달받을 이전 데이터의 개수를 지정합니다. replay가 0이라면 collect 시점에 담겨있던 데이터부터 전달받을 수 있습니다. 만약 1이라면 collect 시점 직전의 데이터부터 전달받으며 시작합니다. 만약 2라면 현재 데이터 이전 두 개의 데이터부터 전달받으면서 시작합니다. 

즉 replay는 collect 시점 이전에 방출 된 데이터의 개수를 전달합니다.

 

extraBufferCapacity란?

buffer 개수 설정을 합니다. flow의 emit이 빠르고 collect가 느릴 때 지정된 개수만큼 buffer에 저장되며 저장된 개수가 넘어가면 onBufferOverflow에 설정된 정책에 따라 동작하게 됩니다.

즉 emit하고 collect의 속도가 다를 때 emit 하는데 collect를 하지 못할 경우 그 개수 만큼 buffer에 저장합니다

 

onBufferOverflow란?

Buffer가 다 찼을 때의 동작을 정의합니다.

- BufferOverflow.SUSPEND : buffer가 꽉 찼을 때 emit을 수행하면 emit 코드가 blocking 됩니다. 즉 buffer의 빈자리가 생겨야 emit 코드 이후의 코드가 수행될 수 있습니다.

- BufferOverflow.DROP_OLDEST : buffer가 꽉 찼을 때 emit을 수행하면 오래된 데이터 부터 삭제하면서 새로운 데이터를 넣습니다.

- BufferOverflow.DROP_LATEST : buffer가 꽉찼을 때 emit을 수행하면 최근 데이터를 삭제하고 새로운 데이터를 넣습니다.

 

기본값은 다음과 같습니다.

@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)
}

 

tryEmit

tryEmit은 코루틴의 suspend 함수 없이도 값을 방출할 수 있게 해줍니다.

collect 하는 곳에서 coroutine을 사용하기 어려운 경우도 있기 때문입니다.

emit 과는 다르게 emit을 시도해서 성공하면 true를 리턴해줍니다.

반응형