StateFlow vs SharedFlow (2)

최희창·2022년 10월 23일

stateIn vs shareIn

기존에 존재하는 cold stream을 hot stream으로 변환하기 위해서는 Flow의 extension function으로 stateIn과 sharedIn이 존재합니다.

// cold stream flow -> SharedFlow로 변경
fun <T> Flow<T>.shareIn(scope: CoroutineScope,
                        started: SharingStarted,
                        replay: Int = 0): SharedFlow<T>
                        
// cold stream flow -> StateFlow로 변경
fun <T> Flow<T>.stateIn(scope: CoroutineScope,
                        started: SharingStarted,
                        initialValue: T): StateFlow<T>
  • 하나의 flow에서 방출되는 결과를 여러 곳에서 동일하게 수신하는 경우 용이하게 사용될 수 있다.
  • 특히 cold flow의 생성 자체에 무거운 작업이 포함되어 있거나 유지하는 작업 자체의 비용이 많이 드는 경우 여러 곳에서 collect를 할 때 매번 생성하여 사용하지 않고 하나의 flow만 생성하고, collect는 여러 곳에서 수신받도록 구현합니다.
  • 즉 방송국처럼 송출은 한군데서 하지만 이를 여러곳에서 수신받는 형태입니다.
  • 예를들어 비싼 비용을 지불해야 하는 network connection을 맺은 후 backend에서 오는 메시지를 flow로 전달받는 형태 같은 경우 적합합니다.
// cold stream
val backendMessages: Flow<Message> = flow {
    connectToBackend() // takes a lot of time
    try {
      while (true) {
          emit(receiveMessageFromBackend())
      }
    } finally {
        disconnectFromBackend()
    }
}

// SharedFlow로 변경
val messages: SharedFlow<Message> = 
              backendMessages.shareIn(scope, SharingStarted.Eagerly)

-> coroutineScope과 SharingStarted param은 stateIn과 sharedIn에서 동일하게 사용됩니다. coroutineScope은 hot stream을 수행하는 coroutine을 생성할 scope을 넘겨주고, SharingStarted는 flow를 실행하는 방법을 세부적으로 조정하기 위해 사용됩니다.

SharingStarted.Eagerly

  • subscriber가 존재하지 않더라도 upstream flow(sharing)은 바로 시작되며 중간에 중지되지 않습니다. 이때 누적되는 값은 replay 개수만큼이며, replay 개수보다 많은 값이 들어오면 바로 버려집니다.(BufferOverflow.DROP_OLDEST로 동작)

SharingStarted.Lazily

  • 첫 번째 subscriber가 등록(collect)한 이후부터 upstream flow가 동작을 시작하며 중간에 중지되지 않습니다. 첫 번째 subscriber는 그 동안 emit된 모든 값들을 얻어가며, 이후의 subscriber는 replay값에 설정된 개수만큼만 얻어가면서 collect를 시작합니다. 구독자가 모두 없어지더라도 upstream flow는 동작을 유지합니다.(이때는 replay 갯수만큼만 cache한다.)

SharingStarted.WhileSubscribed

  • 구독자가 등록되면 sharing을 시작하며 구독자가 전부 없어지면 바로 중지됩니다. 또한 replay 갯수만큼 cache 합니다.
  • stopTimeoutMillis : 구독자가 모두 사라진 이후에 정지할 delay를 넣습니다.
  • replayExpirationMillis : replay를 위해 cache한 값을 유지할 시간을 지정합니다.

Flow lifecycle for Android

  • Flow는 LiveData처럼 android에서 lifecycle에 따라 자동으로 멈추거나 재시작되지 않습니다. 따라서 home키로 화면을 나가더라도 종료되지 않고 계속해서 emit되는 값을 collect합니다.
override fun onCreate(savedInstanceState: Bundle?) {
        ... 
    MainScope().launch {
        testViewModel.connectionFlow.collect {
            Log.i(TAG, "connectionFlow: $it")
        }
    }
        ...
}
  • Livedata처럼 android의 lifecycle에 맞춰 동작하도록 구성하려면 repeatOnLifecycle을 사용합니다.
    (implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            testViewModel.connectionFlow.collect {
                Log.i(TAG, "connectionFlow: $it")
            }    
        }
    }
    ...
}

-> activity가 onStart되는 시점이나 onPause되는 시점에 새로운 coroutine으로 시작되며, onStop 시점에 cancel됩니다.

profile
heec.choi

0개의 댓글