안드로이드 개발을 하면서 대부분 MVVM 패턴을 많이 사용하고 그 안에서 Observable한 형태를 사용하기 위해 LiveData, Rx, Flow등 여러가지를 사용합니다.
그 안에서 ViewModel 내부에서는 Mutable한 형태를 유지하고 외부에서는 Immutable한 형태를 유지하기 위해서 아래와 같은 형태를 많이 사용합니다.
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
sealed interface LoadingUiState {
object Loading : LoadingUiState
object Loaded : LoadingUiState
data class Error(val throwable: Throwable) : LoadingUiState
}
class ViewModel {
private val _loadingUiState: MutableStateFlow<LoadingUiState> = MutableStateFlow(LoadingUiState.Loaded)
val loadingUiState: StateFlow<LoadingUiState> = _loadingUiState.asStateFlow()
}
그런데 저는 LiveData로 사용할 때는 MutableLiveData를 LiveData로 변경하는 asLiveData() 같은 함수를 써본적이 없었습니다.
그래서 문득 asStateFlow()를 왜 사용하는지 궁금해졌습니다.
class ViewModel {
private val _loadingUiState: MutableStateFlow<LoadingUiState> = MutableStateFlow(LoadingUiState.Loaded)
val loadingUiState1: StateFlow<LoadingUiState> = _loadingUiState
val loadingUiState2: StateFlow<LoadingUiState> = _loadingUiState.asStateFlow()
}
아마 지금까지 1번처럼 쓰시던 분들도 많았을 것이고 2번처럼 쓰시던 분들도 많으셨을 것 같습니다.
실제로는 큰 차이가 없을 것 같은데 우선 asStateFlow()의 구현을 보도록 하겠습니다.
/**
* Represents this mutable state flow as a read-only state flow.
*/
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
ReadonlyStateFlow(this, null)
private class ReadonlyStateFlow<T>(
flow: StateFlow<T>,
@Suppress("unused")
private val job: Job? // keeps a strong reference to the job (if present)
) : StateFlow<T> by flow, CancellableFlow<T>, FusibleFlow<T> {
override fun fuse(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow) =
fuseStateFlow(context, capacity, onBufferOverflow)
}
asStateFlow()는 ReadonlyStateFlow 클래스를 StateFlow<T> 타입으로 반환하도록 되어 있습니다.
ReadonlyStateFlow 클래스는 MutableStateFlow 클래스랑 관련이 없는 클래스 입니다.
(viewModel.loadingUiState1 as MutableStateFlow<LoadingUiState>).value = LoadingUiState.Loading // 변경 가능
(viewModel.loadingUiState2 as MutableStateFlow<LoadingUiState>).value = LoadingUiState.Loading // ClassCastException 발생
따라서 asStateFlow()를 사용하지 않는다면 StateFlow를 다시 MutableStateFlow로 캐스팅을 해서 외부에서 value를 set할 수 있게 됩니다.
결국 타입만 바꾸고 객체 자체는 변경되지 않았던 loadingUiState1 방식이 아닌 loadingUiState2 처럼 asStateFlow()를 사용하게 된다면 정말로 Readonly한 StateFlow를 만들 수 있게 됩니다.
안드로이드, 코틀린, 컴포즈 등 개발에 관한 정보를 얻고 싶다면 아래 오픈카톡방에 들어오세요!
'개발 > 코틀린' 카테고리의 다른 글
Kotlin object와 Gson의 오해 (0) | 2023.08.03 |
---|---|
(Test) Junit에서 isEqualTo 와 isSameAs (0) | 2023.06.17 |
(Coroutine) Flow flatMapConcat, flatMapLatest, flatMapMerge (0) | 2023.06.14 |
(코틀린) Testing with Kotest (0) | 2022.12.18 |
(코틀린) 동시성 (0) | 2022.12.11 |