[Kotlin][Coroutine] SharedFlow와 StateFlow - (2) StateFlow, update의 원자성 보장
저번포스트에서 SharedFlow에 대해 알아보았다면, 이번 포스트는 StateFlow에 대해서 알아보자. StateFlow는 SharedFlow의 개념을 확장시킨 것으로, 사실상 replay 값이 1인 SharedFlow와 유사하다.
StateFlow
StateFlow는 읽기 전용 상태를 나타내는 SharedFlow로, 하나의 업데이트 가능한 데이터 값을 가지며, 이 값의 업데이트를 수신자들에게 방출한다. StateFlow 또한 hot flow이기 때문에 하나 이상의 소비자들이 구독할 수 있고, 구독자가 없는 경우에도 데이터를 발행한다 .현재 값은 value 속성을 통해 가져올 수 있다.
- 따라서 초기화할 기본 값(Initial Value)이 필요한 경우에는 StateFlow를 사용하는 것이 적절하다.
- 중단되지 않는 방식(non-suspending way)으로 .value 속성을 사용하여 get 혹은 set할 수 있다.
- 안드로이드에서 livedata의 대체제로 쓰이고, replay가 1인 SharedFlow라고 생각하면 된다
MutableStateFlow
MutableStateFlow는 초기 값을 가진 MutableStateFlow(value) 생성자 함수를 사용해 생성된다.
update, getAndUpdate, updateAndGet은 1.5.1에서 추가된 함수로, atomically(원자적)으로 동작한다는 걸 기억해두자.
Updates the MutableStateFlow.value atomically using the specified function of its value.function may be evaluated multiple times, if value is being concurrently updated.
update
- MutableStateFlow의 값을 안전하게 업데이트하기 위한 함수다.
- 이 함수는 람다를 받아, 현재 값을 기반으로 새로운 값을 계산해 설정한다.
stateFlow.update { currentValue ->
// currentValue를 기반으로 새로운 값을 계산
newValue
}
그렇다면 어떻게 원자성을 보장할까 ? 내부 코드를 보자.
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
while (true) {
val prevValue = value
val nextValue = function(prevValue)
if (compareAndSet(prevValue, nextValue)) {
return
}
}
}
update의 내부를 보면, prevValue와 netValue를 비교해서 값을 업데이트한다. 이를 통해 다양한 스레드에서 접근하더라도 원자성을 유지하고, 값을 안전하게 유지할 수 있다.
getAndUpdate
- 함수는 현재 값을 가져온 후, 새로운 값을 설정하는 작업을 수행한다.
val stateFlow = MutableStateFlow(10)
val oldValue = stateFlow.getAndUpdate { it + 1 }
println(oldValue) // 출력: 10 (업데이트 전의 값)
println(stateFlow.value) // 출력: 11 (업데이트 후의 값)
updateAndGet
- 함수는 새로운 값을 설정한 후, 그 값을 반환하는 작업을 수행한다.
val stateFlow = MutableStateFlow(10)
val newValue = stateFlow.updateAndGet { it + 1 }
println(newValue) // 출력: 11 (업데이트 후의 값)
println(stateFlow.value) // 출력: 11 (업데이트 후의 값)
compareAndSet
- 현재 값이 예상한 값일 때만 새 값을 설정하는 함수다. 설정이 성공하면 true, 실패하면 false를 반환한다.
val success = stateFlow.compareAndSet(expectedValue, newValue)
emit, tryEmit
- emit: 비동기 방식으로 새 값을 방출하는 함수다. 보통 value로 지정하지만, 비동기적으로 값을 방출하고자할때 사용하고, suspend함수이다
- tryEmit: emit의 non-blocking 버전이다. 비동기 작업이 아닌 즉시 값을 방출하려고 할 때 사용하며, 성공하면 true, 실패하면 false를 반환한다.
stateFlow.emit(newValue)
val success = stateFlow.tryEmit(newValue)
private 접근 제어자로 읽기전용 StateFlow만 노출하기
stateFlow는 livedata의 대체제로서 안드로이드 프로그래밍에서 상태를 나타낼 때 자주 쓰인다. 코루틴을 완벽하게 지원하며, 상태를 감지하고, 감지된 상태에 따라서 뷰를 업데이트 하는 용도로 쓰이기 좋다.
이때, stateFlow를 뷰모델에서 사용할때 다음과 같이 _state, state로 정의한다. 이유가 무엇일까 ?
class MyViewModel {
// 내부에서 상태를 변경할 수 있는 MutableStateFlow
private val _state = MutableStateFlow(0)
// 외부에 노출되는 읽기 전용 StateFlow
val state: StateFlow<Int> get() = _state
fun increment() {
_state.value += 1
}
}
private MutableStateFlow 인스턴스를 backing property로 사용하고 해당 인스턴스의 읽기 전용 StateFlow 뷰를 노출한다.
- 캡슐화: MutableStateFlow를 private으로 선언하여 외부에서 직접 값을 변경할 수 없도록 보호한다.
- 읽기 전용 공개: StateFlow를 통해 상태의 현재 값에 대해 읽기 전용 접근만을 허용한다. 외부에서는 상태를 읽을 수 있지만, 임의로 변경할 수는 없도록 하기 위해서이다.
- 안전한 상태 변경: 상태 변경은 클래스 내부에서 제공하는 함수(예: increment())를 통해서만 이루어지도록 한다. 이는 상태 변경이 안전하고 일관되게 이루어지도록 보장한다.
stateIn
stateIn은 Flow를 StateFlow로 변환하여, Flow의 최신 값을 유지하고 필요할 때 언제든지 접근할 수 있도록 한다.
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T>
- scope: CoroutineScope: StateFlow의 생명주기를 관리하는 CoroutineScope이다. 이 범위가 취소되면 StateFlow도 더 이상 값을 방출하지 않는다.
- started: SharingStarted: Flow가 언제 시작되고 중지될지 제어하는 전략을 나타낸다. 일반적인 값으로는 SharingStarted.Lazily, SharingStarted.Eagerly, SharingStarted.WhileSubscribed 등이 있다.
- initialValue: T: StateFlow의 초기 값이다. StateFlow는 항상 초기 값을 가져야 한다.
파라미터의 역할은 sharedFlow의 파라미터와 동일하므로, 생략하겠다.
이렇게 stateFlow에 대해 알아보았는데, stateFlow는 특히 상태 관리가 중요한 UI 개발에 많이 활용되므로 잘 알아두도록 하자.
참고자료
MutableStateFlow
MutableStateFlow A mutable StateFlow that provides a setter for value. An instance of MutableStateFlow with the given initial value can be created using MutableStateFlow(value) constructor function. See the StateFlow documentation for details on state flow
kotlinlang.org
[Android] MutableFlowState의 원자성 보장
정리하기 앞서 안드로이드 클린 아키텍처에 관해 공부 및 코드 분석을 진행하던 중 궁금한 부분이 생겼다. UI와 ViewModel의 상태를 data class와 MutableStateFlow를 겹합하여 상태를 관리하는 구조를 확
sonseungha.tistory.com