Flow (StateFlow / SharedFlow)
Flow / LiveData의 차이를 알고 StateFlow와 ShardFlow 개념을 공부한다.
Flow
1. Flow의 등장 배경
기존에는 콜백과 리스너를 통해 이벤트 발생시 동작을 수행했지만 코드가 복잡해지고 가독성이 떨어지는 문제점이 발생했다. 이런 문제를 해결하기 위해 LiveData, RxJava와 같은 반응형 프로그래밍이 나오게 되었다. UI 갱신을 위해 ViewModel에서 LiveData를 사용했지만 아키텍처 관점에서는 단점을 가지고 있다.
아키텍처 관점에서 LiveData의 단점
- UI와 밀접한 관계가 있기 때문에 Data Layer에서 비동기 방식으로 데이터 스트림을 처리하기엔 한계가 있다.
- 클린아키텍처의 Domain Layer에서 사용하기엔 ACC 라이브러리 의존성을 추가해야하는 문제가 있다.
시간이 흐름에 따라 이런 문제점을 해결 가능한 코루틴과 Flow가 등장하게 되었다.
2. Flow란?
명령형 프로그래밍은 데이터가 필요할 때마다 결과값을 요청해야하는 비효율적인 방식이였다. 이런 문제를 해결하기 위해 반응형 프로그래밍이 등장했다. 코루틴에서는 데이터 스트림을 구현하기 위해 Flow를 사용해야한다.
반응형 프로그래밍 (리액티브 프로그래밍)
외부 환경과 어떻게 커뮤니케이션 하는지에 따라 알아서 반응하는 프로그래밍이다. Pull, Push 두가지 방법이 있다.
- Pull : 소비자(이벤트 처리)가 발행자(데이터 생성)를 감시한다.
- Push : 발행자(데이터 생성)가 변경 된 데이터를 소비자(이벤트 처리)에 알려준다.
리액티브 프로그래밍은 발행자와 소비자가 존재하고 지속적으로 소비자에게 데이터를 전달하는 역활을 한다. (데이터 스트림)
이벤트 소스
- flowOf() : 최초 들어온 값만 Flow로 생성한다.
- asFlow() : List, Map 등 기존 컬렉션을 Flow로 변환한다.
- callbackFlow(): 비동기 작업을 수행할때 유용하다.
연산자
- map
- filter
- transform
- take
- zip : 두개 이상의 Flow를 결합 해 하나의 Flow로 변환해 방출한다.
- onEach
구독자
- collect
- toList
- toSet
- first
- reduce
- fold
- catch
- onCompletion
3. LiveData vs Flow
Activity, Fragment 처럼 UI에서만 사용된다면 Lifecycle을 따라 관리 해주는 LiveData를 사용하는 것이 좋지만 다른 레이어에서는 사용 하기에는 제약이 있기 때문에 Flow를 사용하는 사용하는 것이 적절할 수도 있다.
LiveData | Flow | |
초기값 | 초기값 불필요 | 초기값 필요 |
라이브러리 | 안드로이드 플랫폼에 종속적 | 순수 Kotlin 라이브러리 |
생명주기 | 자동으로 관리 | 알수없음 |
코루틴 | 통합 어려움 | 통합되어 사용 |
백프레셔 | 처리 불가능 | 처리 가능 |
4. LiveData를 Flow로 대체하기 어려운 이유
Flow는 스스로 생명주기에 대해 알 수 없기 때문에 Lifecycle에 따른 중지나 재개가 어렵다. 또, Flow는 상태가 없어 값이 할당되었는지 되었다면 어떤 값인지 알기가 어렵다는 문제점을 가지고 있다. 이런 문제를 해결하기 위해 SharedFlow와 StateFlow가 등장하게 된다.
ColdFlow / Hot Flow
1. Cold Flow(Flow) / Hot Flow(StateFlow, SharedFlow) 차이점
Cold Flow
- 소비자가 있어야 방출을 시작한다.
- 데이터는 Flow의 내부에서 생성된다.
- 상태를 가질 수 없기 때문에 데이터 홀더의 역할을 할 수 없다.
- 여러 소비자가 하나의 Flow 데이터의 구독이 가능하지만 각각의 소비자가 독립적이다.
여러 소비자가 서로 다른 타이밍에 데이터를 요청해도 동일한 결과 값을 방출한다. - 소비자가 소비(collect)를 시작할때 데이터를 생산한다.
Hot Flow
- 소비자가 없어도 방출을 시작한다.
- vale 속성을 이용해 외부에서 데이터를 넣거나 변환이 가능하다.
- 데이터 홀더 클래스 역할이 가능하다.
- 여러 소비자가 하나의 Flow 데이터의 구독이 가능하지만 모든 소비자가 상호관계이다.
가장 최근의 값을 방출한다. - 초기값을 가지게 된 순간 데이터를 방출한다.
StateFlow는 초기값을 가지고 있기 때문에 구독을 시작한 순간 방출, SharedFlow는 초기값을 지정한 순간 방출
.stateIn()
ColdFlow를 HotFlow로 변환해 준다.
Data Holder Class
Data 변경을 관찰 할 수 있는 클래스
StateFlow
1. StateFlow의 등장 배경
Flow는 데이터의 흐름만 발생시키고 데이터를 저장할 수는 없다. flow를 이용해 UI를 업데이트하기 위해서는 화면이 재구성될때마다 다시 서버/DB에서 데이터를 가져오던가 viewModel에 저장하고 사용하는 방법을 이용해야했다. 비효율적(뷰가 다시 그려지는 경우마다 새롭게 값을 가져와야한다.), VIewModel이 모든 UI의 데이터들을 저장해야하기 때문에 보일러 플레이트 코드를 만들어 내는 문제가 있다. 이런 문제점을 해결하기 위해서 StateFlow가 등장하게 되었다.
2. StateFlow란?
stateFlow는 view의 생명주기를 모르기 때문에 background에 앱이 내려간 상황에서도 계속 값을 받아온다. 이런 문제를 해결하기 위해 Lifecycle.State.*를 사용해준다. 어느 생명 주기에 값을 받아올것인지 지정해주게 된다면 해당 lifecycle에만 본문을 타게 된다.
private fun collectLifecycle() = lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
// stateFlow는 1:1 관계이기 때문에 코루틴스코프를 각자 만들어줘야한다.
launch {
homeViewModel.todayExercises.collect {
it?.let {
binding.topInformationLayout.todayMoveTimeCountTextView.text =
it.exerciseTime
binding.topInformationLayout.todayWorkCountTextView.text = it.distance
}
}
}
launch {
homeViewModel.recommSearch.collect {
it?.let {
binding.topInformationLayout.todayCardTitleTextView.text = it.title
}
}
}
}
3. StateFlow vs SharedFlow
stateFlow는 sharedFlow를 상속 받고 있다.
sharedFlow는 여러 버퍼를 저장하고 있고 버퍼가 가득 찼을 경우 어떤 버퍼를 버릴것인지 선택하는 옵션을 주어야한다. 이벤트성인 경우 사용한다. (ex. 값도 가지고 있어야하고, 네트워크 오류가 발생했을 경우 팝업을 띄어줘야한다.)
stateFlow는 1개의 값만 저장하고 오래된 값을 제거하는 옵션을 가지고 있다. 화면에 값을 표기하거나 가지고만 있다면 sharedFlow보단 stateFlow를 사용하는 것이 적절하다.
StateFlow는 신규 구독자에게 마지막 값을 방출해주지만 SharedFlow는 신규 구독자에게 마지막 값을 방출 해주지 않는다. 만일 기존에 방출되고 있는 값을 알고 싶다면 StateFlow를 사용하는게 적절하다.