StateFlow vs SharedFlow (1)

최희창·2022년 10월 23일

설명에 앞서

  • Cold stream : collect()를 호출할 때마다 flow block이 재실행된다.
  • Hot stream : collect()를 호출하더라도 flow block이 호출되지 않고 collect() 시점 이후에 emit된 데이터를 전달 받는다.

-> StateFlow, SharedFlow는 Hot stream입니다.

State flow

  • 현재 상태를 표현하기 적합한 flow입니다.
  • StateFlow는 초기값이 필요합니다.
private val _stateFlow = MutableStateFlow(99)
val stateFlow = _stateFlow

// state flow에 데이터 전달
suspend fun startSendDataToStateFlow() {
    repeat(10) {
        _stateFlow.value = it
        //_stateFlow.emit(it) 으로도 사용 가능
        delay(500)
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
...
     MainScope().launch {
        testViewModel.stateFlow.collect {
            Log.i(TAG, "stateFlow #1: $it - ${Thread.currentThread().name}")
        }
        Log.d(TAG,"State Collect End #1 - ${Thread.currentThread().name}")
    }
    
    // emit 시작
    MainScope().launch {  testViewModel.startSendDataToStateFlow() }
}

-> collect 후 값을 emit해주고 있으므로 collect 시점에서는 아직 방출된 데이터가 없기 때문에 기본값을 받아갑니다. 따라서 99부터 수신되며 그 이후 방출된 0~9의 데이터가 collect를 통해 수신되게 됩니다.
(만약 emit이 먼저 호출된 이후 collect 된다면 초기값이 99가 아닌 이미 stateFlow에 담겨져있는 0부터 전달받게 됩니다.)

  • 즉 각 collect가 호출되면 각 collect를 시작한 시점에서 각각의 데이터를 수신합니다.

Shared Flow

  • StateFlow에 비해 좀 더 detail한 설정을 할 수 있습니다.
private val _sharedFlow = MutableSharedFlow<Int>(
        replay = 0,
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
val sharedFlow = _sharedFlow

replay : collect시 전달받을 이전 데이터의 갯수를 지정합니다.

ex) 0->1->2->3-> collect 시작->4->5->6->7->8->9

  • replay = 0 : 4부터 수신 시작
  • replay = 1 : 3부터 수신 시작
  • replay = 4 이상 : 0부터 수신 시작

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

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

  • BufferOverflow.SUSPEND : buffer가 꽉 찼을 때 emit을 수행하면 emit 코드가 blocking 됩니다. 즉, buffer의 빈자리가 생겨야 emit 코드 이후의 코드가 수행될 수 있습니다.
  • BufferOverflow.DROP_OLDEST : buffer가 꽉 찼을 때 emit을 수행하면 오래된 데이터 부터 삭제하면서 새로운 데이터를 넣습니다.
  • BufferOverflow.DROP_LATEST : buffer가 꽉 찼을 때 emit을 수행하면 최근 데이터를 삭제하고 새로운 데이터를 넣습니다.
private val _sharedFlow = MutableSharedFlow<Int>(
    replay = 0,
    extraBufferCapacity = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val sharedFlow = _sharedFlow

// SharedFlow에 데이터 전달
suspend fun startSendDataToSharedFlow() {
    repeat(10) {
        _sharedFlow.emit(it)
        delay(500)
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
    ...

    MainScope().launch { testViewModel.startSendDataToSharedFlow() }

    MainScope().launch {
        testViewModel.sharedFlow.collect {
            Log.i(TAG, "sharedFlow #1: $it - ${Thread.currentThread().name}")
        }
        Log.d(TAG,"Shared Collect End #1 - ${Thread.currentThread().name}")
    }
...

-> emit 이후 collect를 수행하므로 collect 이후 수신되는 값인 1부터 수신받게 됩니다.

SharedFlow vs StateFlow

  • StateFlow는 sharedFlow의 replay을 1로 고정시켜 놓은 형태라고 볼수 있지만 그외에 더 큰 차이가 있습니다.
// StateFlow 생성
private val _stateFlow = MutableStateFlow(99)
val stateFlow = _stateFlow

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

// 양쪽 flow에 "100" 을 다섯번 반복 전송
suspend fun repeatSameDataToEachFlow() {
    repeat(5) {
        Log.d(TAG, "sendData #$it")
        _sharedFlow.emit(100)
        _stateFlow.value = 100
        delay(500)
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
 ...
    // sharedFlow collect
    MainScope().launch {
        testViewModel.sharedFlow.collect {
            Log.i(TAG, "sharedFlow: $it")
        }
    }
    
    // stateFlow collect
    MainScope().launch {
        testViewModel.stateFlow.collect {
            Log.i(TAG, "stateFlow: $it")
        }
    }

    // 양쪽 flow에 emit 시작
    MainScope().launch {
        testViewModel.repeatSameDataToEachFlow()
    }
}

-> sharedFlow, stateFlow 양쪽에 100이란 값을 5번 반복해서 전송합니다.
결과는

  • stateFlow : 99(초기값) -> 100 출력
    (총 2번 출력)
  • sharedFlow : 100이 5번 그대로 출력

즉 동일한 값이 입력되면 stateFlow의 경우 내부적으로 skip하여 collect 자체가 호출되지 않습니다.

profile
heec.choi

0개의 댓글