개발/코틀린

(Coroutine) asStateFlow()를 써야할까요?

DinoDev 2023. 7. 28. 17:24
728x90
반응형

안드로이드 개발을 하면서 대부분 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를 만들 수 있게 됩니다.

 


안드로이드, 코틀린, 컴포즈 등 개발에 관한 정보를 얻고 싶다면 아래 오픈카톡방에 들어오세요!

 

Android Kotlin Compose QnA

 

open.kakao.com

 

728x90
반응형