관리 메뉴

나만을 위한 블로그

[Android] stateIn이란? 본문

Android

[Android] stateIn이란?

참깨빵위에참깨빵 2024. 5. 17. 18:38
728x90
반응형

stateIn은 Flow를 StateFlow로 바꾸기 위해 사용하는 함수다. 코틀린 문서의 설명은 아래와 같다.

 

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/state-in.html

 

stateIn

Converts a cold Flow into a hot StateFlow that is started in the given coroutine scope, sharing the most recently emitted value from a single running instance of the upstream flow with multiple downstream subscribers. See the StateFlow documentation for th

kotlinlang.org

콜드 플로우를 지정된 코루틴 스코프에서 시작하는 핫 플로우로 바꿔서 업스트림 플로우의 실행 중인 단일 인스턴스에서 가장 최근에 방출된 값을 여러 다운스트림 구독자와 공유한다. 공유 코루틴의 시작은 started 매개변수에 의해 제어된다. stateIn 연산자는 일부 상태 값에 대한 업데이트를 제공하는 콜드 플로우가 있고, 생성 및 유지관리 비용이 많이 들지만 가장 최근 상태값을 수집해야 하는 구독자가 여러 명 있는 경우에 유용하다
예를 들어 비용이 높은 네트워크 연결을 통해 백엔드에서 오는 상태 업데이트 플로우가 있는데, 설정에 많은 시간이 걸리는 경우를 생각하라. 개념적으론 아래와 같이 구현할 수 있다
val backendState: Flow<State> = flow {
    connectToBackend() // takes a lot of time
    try {
      while (true) {
          emit(receiveStateUpdateFromBackend())
      }
    } finally {
        disconnectFromBackend()
    }
}
이 플로우가 앱에서 직접 사용되는 경우 수집될 때마다 새 연결이 설정되고 상태 업데이트가 흐르기 시작하기까지 시간이 걸린다. 하지만 이렇게 하나의 연결을 공유해서 빠르게 설정할 수 있다
val state: StateFlow<State> = backendMessages.stateIn(scope, SharingStarted.Eagerly, State.LOADING)
이제 상태의 모든 수집기 간에 단일 연결이 공유되며, 연결이 필요할 때 이미 연결이 설정돼 있을 가능성이 높다

< 매개변수 >

scope : 공유가 시작되는 코루틴 범위
started : 공유 시작, 중지 시점을 제어하는 전략
initialValue : StateFlow의 초기값. 이 값은 replayExpirationMillis 매개변수와 함께 SharingStarted.WhileSubscribed 전략을 통해 상태 흐름을 재설정할 때도 사용된다

 

콜드 플로우를 내가 지정한 CoroutineScope에서 시작하는 핫 플로우로 바꾼 다음 여러 구독자가 공유할 수 있도록 하는 게 stateIn이란 것 같은데 잘 이해가 안 된다.

다른 곳에선 어떻게 설명하는지 확인해 봤다.

 

https://medium.com/@mortitech/sharein-vs-statein-in-kotlin-flows-when-to-use-each-1a19bd187553

 

ShareIn vs StateIn in Kotlin Flows: When to Use Each

Kotlin Flows are a powerful tool for building reactive and asynchronous applications. With Flows, you can easily handle streams of data…

medium.com

stateIn은 stateful shared flow를 생성할 수 있는 Flow 함수로 최신 값을 캐싱해서 새 수집기에 즉시 제공한다. 이는 여러 컴포넌트가 같은 데이터에 접근해야 하는 UI 개발 같이 앱의 여러 부분에서 상태를 관리, 공유하는 데 특히 유용하다. 이 함수는 지정된 초기값, 특정 공유 동작을 가진 새 Hot Flow를 만든다. 또한 value 속성을 사용해 동기적으로 접근할 수 있는 현재 값을 가진 Hot Flow 유형인 StateFlow 객체를 리턴한다. 따라서 업스트림 소스에서 업데이트할 수 있는 현재 값을 가진 플로우를 공유해야 할 때 유용하다
stateIn을 사용할 땐 상태 공유가 이뤄져야 하는 CoroutineScope, Flow가 데이터를 공유하기 시작하는 시기와 방법을 결정하는 공유 전략(started), 아직 값이 생성되지 않았을 때 방출되는 초기 상태를 제공해야 한다

 

추가로 업스트림 소스, 다운스트림 소스의 뜻은 각각 아래와 같다.

 

  • 업스트림 소스 : 데이터 스트림에서 데이터 or 이벤트를 만드는 시작 지점. 데이터를 방출하는 원천이다. 다운스트림 소비자(구독자)가 데이터를 요청하면 데이터를 제공한다
  • 다운스트림 소스 : 업스트림 소스로부터 데이터를 받아서 처리하거나 소비하는 부분. 유저에게 최종 결과를 제공할 수도 있다

 

쉽게 말해서 데이터를 만드는 쪽이냐, 받는 쪽이냐의 차이다.

본론으로 돌아와서, stateIn에 대해 찾아봤지만 디벨로퍼에서도 그렇고 다른 대부분의 블로그에서도 Flow를 StateFlow로 변환한다 정도의 설명만 있어서, 이 정도로만 이해하고 넘어간다.

 

아래는 stateIn의 사용 예시다. 뷰모델에 아래와 같이 에러 표시 여부를 StateFlow 타입으로 갖는 경우를 가정한다.

 

val showIdError: StateFlow<Boolean> = combine(_id, _idTouched) { id, idTouched ->
    idTouched && !ValidationUtils.isIdValid(id)
}.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = false
)

 

combine 함수는 둘 이상의 Flow를 합쳐서 최신 값을 Flow 타입으로 리턴하는 함수다.

이 때 stateIn을 추가하지 않고 사용하려고 하면 Type mismatch 에러가 발생한다.

 

Type mismatch
Required : StateFlow<Boolean>
Found : Flow<Boolean>

 

UI에서 사용하려면 Flow 형태여선 사용할 수 없다. StateFlow, SharedFlow 중 하나의 타입이어야 한다.

위 예시에선 StateFlow로 변환하기 위해 stateIn()을 사용해서 Flow를 StateFlow로 바꿔서 뷰에서 가져다 사용할 수 있게 한다.

만약 SharedFlow로 바꾼다면 shareIn()을 사용한다. 아래는 액티비티에서 showIdError를 사용하는 예시다.

 

lifecycleScope.launch {
    myViewModel.showIdError.collect { showError ->
        // showError를 활용하는 로직
    }
}

 

반응형
Comments