본문 바로가기
Mobile App

[안드로이드] - Flow (coroutine) - 0

by Jman 2023. 1. 18.

안드로이드에서 비동기 처리를 위해서 코루틴을 사용한다.

우리는 개발 시, LiveData 대신하여 Flow 를 사용할 수 있다.

하지만, Flow 는 스스로 라이프사이클을 알지 못하기 때문에 CoroutineScope 를 잘 이해하고 사용하여 불필요한 메모리 사용을 방지해야 한다.

 

LiveData VS Flow

LiveData 는 UI 에 밀접하게 연관되어 있기 때문에 Data Layer 에서 비동기 방식으로 데이터를 처리하기에 자연스러운 방법이 없다.

또한 LiveData 는 안드로이드 플랫폼에 속해 있기 때문에 순수 Java / Kotlin 을 사용해야 하는 Domain Layer 에서 사용하기에 적합하지 않다.

반면 Flow 는 스스로 안드로이드 생명주기에 대해 알지 못하여, 라이프사이클에 따른 중지나 재개가 어렵다

 

Coroutine Flow

Flow 는 코루틴의 데이터 스트림이다.

코루틴 상에서 리액티브 프로그래밍을 지원하기 위한 구성요소라 생각하면 된다.

 

리액티브 프로그래밍이란 무엇인가?

데이터가 변경될 때, 이벤트를 발생시켜 데이터를 계속해서 전달한다.

  • 명령형 : 사용자는 데이터를 요청하고 일회성으로 결과값을 수신데이터가 필요할 때마다 결과값을 요청해야 한다.
  • 반응형 : 데이터를 발행하는 주체가 있고, 소비자는 구독을 하고, 발행자는 새로운 데이터가 들어오면 소비자에게 지속적으로 발행한다

위 두 가지 타입을 보았을 때, 명령형은 비효율적이다. 따라서, 리액티브 프로그래밍을 적용하자.

 

 

Flow 데이터 스트림

  • Producer (생산자) : 스트림에 추가되는 데이터를 생산한다. 코루틴 덕분에 흐름에서 비동기적으로 데이터가 생산될 수도 있다.
  • Intermediary (중간 연산자) : 스트림에서 내보내는 각각의 값이나 스트림 자체를 수정할 수 있다.
  • Consumer (소비자) : 스트림의 값을 사용한다.

 

 

DataStream : Producer

생산자는 데이터를 발행하는데, 주로 local 또는 remote 의 DataSource 에서 데이터를 가져온다.

그리고 emit() 함수를 통하여 데이터를 생성한다.

class RemoteDataSource(
        private val remoteApi: RemoteApi
) {
        // 먼저 flow scope를 선언
        fun getObjectFlow(): Flow<List<Object>> = flow {
                while(true) {
                    val model = remoteApi.fetchLastedObject() // remote 서버로 부터 데이터를 받아옴
                    emit(model)   
                    delay(60000) 
                }
        }
}

DataStream : Intermediary

생성된 데이터를 중간 연산자에서 데이터를 수정한다.

여기서 생성자가 A 라는 객체의 데이터를 발행했지만,

B라는 객체 데이터가 필요한 경우, Flow 에서 지원하는 중간 연산자를 통해 A 객체를 B 객체로 데이터 변경을 한다.

map, filter, onEach 을 이용하여 데이터를 수정한다.

class ObjectRepository(
        private val objectRemoteDataSorce: ObjectRemoteDataSorce
) {
        fun getObjectOfViewItem(locale : Locale) =
                objectRemoteDataSorce.getObjectFlow().map{ it.filter (this.property == property) 
}

 

DataStream : Consumer

중간 연산자가 생성한 데이터를 가공하여 소비자로 데이터를 전달한다.

안드로이드에서 소비자는 UI 구성(view) 요소로 생각하면 된다.

collect 를 이용해 전달된 데이터를 소비할 수 있다.

class ObjectViewModel(
        private val objectRepository: ObjectRepository
) : ViewModel() {
        fun collectObjectOf(model: UIModel) =
                viewModelScope.launch {
                        dustRepository.getObjectFlow().collect { model ->
                                text = model.property
                        }
                }
        }
}

 

 

Flow 의 한계

아쉽게도 Flow 는 데이터의 흐름이지, 해당 데이터를 return 받아 저장할 수 없다.

따라서, flow 만을 이용하여 UI state 를 업데이트 하기 위해서는 collect 한 데이터를 viewmodel 에 저장해놓는 방법이 있다.

 

flow 는 데이터를 구독하고, 데이터 홀더 변수는 flow 에서 마지막으로 발행한 데이터를 저장하고 있으면 된다.

따라서, flow 에서 값을 발행하기 전에는 데이터 홀더 변수의 데이터를 사용하면 된다.

 

하지만, 보일러 플레이트 코드로 인한 가독성 저해 이슈가 생긴다.

 

StateFlow (Hot Stream)

이는 flow 의 데이터 홀더 역할을 하면서 flow 의 데이터 스트림 역할까지 한다.

즉, 현재 상태와 새로운 상태 업데이트를 collector 에 내보낼 수 있는 observeable flow 이다.

또한 value 프로퍼티로 현재 상태의 값을 읽어서 사용할 수 있다.

 

UI Layer (View)

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                latestNewsViewModel.uiState.collect { uiState ->
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

UI Layer (ViewModel)

sealed class LatestNewsUiState {
    data class Success(news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(exception: Throwable): LatestNewsUiState()
}

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews.collect { favoriteNews ->
                _uiState.value = LatestNewsUiState.Success(favoriteNews)
            }
        }
    }
}

 

위 코드를 확인하면, 결국 View 에서 viewmodel stateFlow 를 받아와, 수신대기하고

uiState 의 value 에 따라, UI 를 업데이트한다.

 

 

StateFlow, Flow, LiveData

StateFlow 와 LiveData 는 비슷한 점이 있다.

둘 다 관찰 가능한 데이터 홀더 클래스라는 점이다.

그러나, StateFlow 와 LiveData 는 다르게 동작한다.

StateFlow 의 경우, 초기 상태를 생성자에 전달해야 하지만? LiveData 의 경우는 전달하지 않는다.

또한,

뷰가 STOPPED 상태가 되면, LiveData.observe() 는 소비자(UI)를 자동으로 등록 취소하는 반면, StateFlow 와 Flow collect 를 하는 경우 자동으로 collect 를 중지하지 않는다.

따라서, 동일한 동작을 실행하려면 Lifecycle.repeatOnLifecycle 블록에서 흐름을 수집해야 합니다.