본문 바로가기

Kotlin

StateFlow, SharedFlow 제대로 알기

Unsplash, Solen Feyissa.

 

Flow 는 단일 수신자에게 스트림을 제공하는 일반적인 방식입니다.

그럼, 수신자가 여럿인 경우는 어떻게 하면 좋을까요? StateFlowSharedFlow 는 이러한 상황에 대한 좋은 선택지가 될 수 있습니다.

 


 

🤔 얘넨 또 뭐하는 애들인가요?

 

StateFlow 와 SharedFlow 는 상태의 업데이트를 최적화하고, 여러 소비자에게 값을 보낼 수 있도록 하는 Flow API 입니다. 여러 상황에 다이나믹하게 도입할 수 있어, 유연한 스트림 전송을 도와줍니다.

 

StateFlow

수집자에게 현재의 상태와 새로운 상태 업데이트를 방출하는 State-Holder Observable Flow 입니다.

State-Holder 라는 것은 상태를 보관할 수 있음을 의미합니다. 시간의 흐름에 따라 달라질 수 있는 특정 상태에 따라 프로그램의 행위를 정의할 수 있습니다.

 

Observable 은 LiveData 덕에 익숙할 겁니다. 상태의 변화를 Observing 할 수 있고, 이를 통해 상태 변화에 따라 특정 작업을 수행하면 되겠습니다.

 

선언법은 다음과 같습니다.

 

class FlowClass() {
    private val _testFlow = MutableStateFlow(5) // 초기값 5
    val testFlow: StateFlow<Int>
        get() = _testFlow
}

 

현재 상태값은 value 프로퍼티로 참조할 수 있습니다.

직접 접근하는 것 같지만, getter 를 두었기 때문에 직접 접근하는 건 아닙니다.

 

fun doSomething() {
    val currentState = testFlow.value
}

 

다음과 같은 방식으로도 구현이 가능하겠죠?

 

fun doSomething() {
    when(testFlow.value) {
        1 -> {
            // show some dialog
        }
        2 -> {
            // intent to other activity
        }
        3 -> {
            // show snackbar
        }
        else -> {
            // quit the app
        }
    }
}

 

위는 고정된 프로퍼티에 대한 참조이며, 다음 예제는 연속적으로 들어오는 값에 대한 Observing 입니다.

 

fun doSomething() {
    CoroutineScope(Dispatchers.IO).launch {
        viewModel.stateFlow
            .onEach {
                println(it)
            }.collect()
    }
}

 

위와 같이 구현하면 매번 들어오는 값에 대해 프린트할 수 있습니다.

 

StateFlow 는 상태의 변경이 있고, 이를 관찰할 수 있어야 할 때 적합합니다.

 


 

HotStream

StateFlow 는 일반 Flow 와 다르게 HotStream 입니다. Flow 에서 수집하려 해도 생산자 코드가 트리거되지 않습니다. 계속해서 흘러나옵니다. 즉, 메모리에 계속해서 탑재되어 있고, 참조가 없는 경우에만 메모리에서 해제됩니다.

 

새로운 소비자가 해당 StateFlow 에 대한 수집을 실행하면 현재 상태를 먼저 반환하고, 그 이후로 방출되는 값을 지속적으로 전달받게 됩니다. LiveData 와 똑같습니다.

 

stateIn()

Collections 와 Sequence 에 asFlow() 메서드를 통해 flow 로 바꾸었듯이, 모든 flow 는 stateIn() 메서드를 통해

StateFlow 로 바꾸어 줄 수 있습니다.

 

 

StateFlow 는 LiveData 와 닮아 있습니다. 둘 다 관찰 가능한 Data-Holder 입니다. 앱 아키텍쳐에 적용되는 모습도 비슷합니다. 그러니, LiveData 를 사용하듯 사용하면 될 것 같습니다. 물론 정답은 없습니다.

비슷한 두 클래스의 차이점은 다음과 같습니다.

LiveData 는 초기값이 없어도 되지만, StateFlow 는 꼭 초기값이 있어야합니다.

LiveData 는 앱 컨테이너의 수명주기를 따르지만, StateFlow 는 아닙니다.

만약 수명주기를 따르게 하고 싶다면 (수명주기에 따라 알아서 생성 및 해제 되도록 하고 싶다면) Lifecycle.repeatOnLifeCycle 블록에서 Flow 를 수집하면 되겠습니다.

 


 

SharedFlow

 

SharedFlow 는 여러 소비자에게 스트림을 보내기 위해 사용될 수 있습니다. StateFlow 와의 차이점은, 캐시와 버퍼를 가지고 있어, 뒤늦게 수집을 시작한 소비자에게도 전체 값을 보낼 수 있다는 점입니다.

선언법은 다음과 같습니다.

 

class FlowClass() {
    private val _testFlow = MutableSharedFlow<Int>()
    val testFlow: SharedFlow<Int>
        get() = _testFlow
}

 

SharedFlow 의 프로퍼티

 

SharedFlow 의 프로퍼티는 다음과 같습니다.

 

private val replay: Int,
private val bufferCapacity: Int,
private val onBufferOverflow: BufferOverflow

 

replay replayCache 의 capacity 입니다. 가령, 이를 10으로 설정하면 과거 10개의 값을 캐시에 저장해둘 수 있습니다. replayCache 는 관련 함수로 캐시 내에 무엇이 있는지 확인할 수 있고, 비울 수도 있습니다.

기본값은 0입니다.

bufferCapacity 역시 단순히 버퍼의 용량이며, 10으로 설정하면 필요시 10개의 값을 버퍼에 할당할 수 있습니다.  

onBufferOverflow 는 BufferOverflow 발생시 대응 전략을 설정할 수 있습니다.

기본적으론 SUSPEND 이며, DROP_OLDEST, DROP_LATEST 가 있습니다.

 

shareIn()

stateIn() 과 같이 shareIn() 메서드를 사용하면 flow 를 SharedFlow 로 바꾸어줄 수 있습니다.

 

HotStream

SharedFlow 역시 HotStream 입니다. 계속해서 흘러나옵니다. StateFlow 와 똑같이 메모리에 계속해서 탑재되어 있고, 참조가 없는 경우에만 메모리에서 해제됩니다.

 


 

🤔 StateFlow 와 SharedFlow  를 적재적소에 사용하는 법?

간단합니다.

 

업데이트되는 특정값이 지속적으로 필요하다 -> StateFlow

받아온 값들에 대한 일시적 저장 및 과거의 값에 접근할 필요가 있다 -> SharedFLow