Flow는 LiveData의
- Android 플랫폼 종속적이고 UI가 없는 곳에서 LiveData를 사용하기가 어렵다
- 언어 의존성(kotlin)만 지니는 domain layer 에서는 liveData를 쓰기 어렵다
위의 두 문제를 해결하기위해 탄생함
즉, Flow를 사용하면
- 클린아키텍처 관점에서 LiveData는 플랫폼 종속적이므로 Domain 계층에 사용할 수 없지만, Flow는 Domain 계층에 사용할 수 있다
- 결과를 필터링하는 등의 다양한 기능을 하는 함수들을 사용할 수 있다
크게 위와같은 장점이 있다
# Flow
fun simple(): Flow<Int> = flow { // flow builder
for (i in 1..3) {
delay(100) // pretend we are doing something useful here
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
// Launch a concurrent coroutine to check if the main thread is blocked
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
// Collect the flow
simple().collect { value -> println(value) }
}
기본적인 Flow의 구조는 emit() 으로 데이터를 배출하고,
Collect로 데이터를 받아오는 방식이다. 위 과정은 비동기로 처리된다.
위 과정을 살펴보면 마치 LiveData를 구독하여 관찰하는 것과 유사한 형태를 띈다는 것을 알 수 있다.
하지만 Flow는 Livedata와 달리 3가지 차이가 존재한다.
1. Flow는 상태가 없다. 그래서 현재 값을 알지못한다
2. Flow는 Cold Stream 방식으로, 연속해서 들어오는 데이터를 처리할 수 없고 collect할 때마다 flow가 재실행된다
3. Flow는 안드로이드 생명주기(Lifecycle)에 대해 알지못한다. 따라서 생명주기에 따른 다른 처리가 필요하다
-> 따라서 1,2번 문제 보완하기위해 -> StateFlow, SharedFlow
-> 3번 문제 보완하기위해 -> launchWhenStarted... 사용
- Cold Stream (Flow)
-> 하나의 소비자가 구독 시 생성되며, 소비할 때마다 생성되어 데이터가 발행됨
-> 즉, 하나의 Flow 빌더에 여러개의 소비자가 collect를 요청하면, 하나의 collect 마다 데이터를 호출하기 때문에 비용적으로 비효율적
-> .value의 형태로 값 얻기 불가능
- Hot Stream (StateFlow, SharedFlow)
-> 하나 이상의 소비자들이 구독할 수 있고, 데이터 발행이 시작되면 모든 소비자들에게 같은 데이터를 발행함
-> 즉, 소비자의 collect 요청이 없어도 바로 값을 내보내고, 데이터가 업데이트 될 때마다 데이터를 발행
-> .value의 형태로 값 얻기 !가능!
# LiveData
비교를 위해 LiveData를 먼저 사용해보자
class MyViewModel : ViewModel() {
private val _liveData = MutableLiveData(100)
val liveData: LiveData<Int> = _liveData
fun changeLiveData(){
viewModelScope.launch {
repeat(10){
_liveData.value = _liveData.value?.plus(1)
delay(1000L)
}
}
}
}
뷰모델에서 LiveData를 생성하고
10번 반복해서 값을 바꿔주는 ChangeLiveData 메소드를 정의한다
class MainActivity : AppCompatActivity() {
private val myViewModel : MyViewModel by viewModels()
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 1. LiveData
myViewModel.liveData.observe(this){
binding.textView.text = it.toString()
Log.d("test5", "LiveData : $it")
}
}
메인액티비티에서 옵저버패턴을 통해 LiveData를 관찰하고, 바인딩을 통해 뷰를 갱신해준다
MainActivty를 실행할때마다 값이 잘 갱신될것이다
# StateFlow
StateFlow는 Flow와 달리 상태(Value)를 가지고, Hot Stream 이다.
또한 초기값을 가진다 ( 초기 값을 생성자에 전달해줘야함)
Flow는 Cold stream 방식 -> Colde Stream이란 재생버튼을 눌렀을 때 맨 앞에서 재생되는 라디오 같이 Collect를 수행할 때 맨 앞의 값부터 읽어 오게 된다. Flow가 만들어 졌어도 Collect 되기전까지는 이 값이 소비되지 않는다
StateFlow는 Hot stream 방식 -> collector가 없어도 생성 시 바로 활성화되며, 값이 업데이트 된 경우에만 반환한다.
StateFlow를 사용하여 LiveData에서 작성한 코드와 동일한 기능을 하는 코드를 작성해보자
class MyViewModel : ViewModel() {
private val _stateFlow = MutableStateFlow(200)
val stateFlow = _stateFlow.asStateFlow()
fun changeStateFlow(){
viewModelScope.launch {
repeat(10){
_stateFlow.value = _stateFlow.value.plus(1)
delay(1000L)
}
}
}
}
뷰모델에서는 LiveData만 stateFlow로 바뀐 것과
MutableStateFlow(200)을 설정해서 초기값을 200으로 준것 이외에 크게 차이가 없다
class MainActivity : AppCompatActivity() {
private val myViewModel : MyViewModel by viewModels()
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 2. StateFlow
lifecycleScope.launchWhenStarted {
myViewModel.stateFlow.collectLatest {
binding.textView2.text = it.toString()
Log.d("test5", "StateFlow : $it")
}
}
}
}
메인액티비티에서는 Flow는 라이프사이클을 알지 못하기 때문에, lifeCycleScope에서 launchWhenStarted 를 사용해 라이프사이클 동안 구독하도록 하고,
collectLatest를 통해 마지막에 변경된 값(가장 최신의 값)을 관찰해 갱신해서 보여주도록 한다.
동일하게 MainActivty를 실행할때마다 값이 잘 갱신되는것을 확인할 수 있다
위 과정을 살펴보면, StateFlow는 LiveData와 별차이가 없어보인다
하지만 클린아키텍처 관점에서 큰 차이가 있다
클린아키텍처에 대해 간단히 서술하자면
안드로이드에서의 클린아키텍처의 구조는
1. UI(Presentation) 계층 - View, ViewModel...
2. Domain 계층 - Repository (interface), UseCase...
3. Data 계층 - Repository (implement), DateSource...
로 나뉜다. 의존성 관계를 명확히하여 유지보수, 동작구조 파악을 쉽게 하기 위함이다
Domain 계층은 UI, Data 계층으로의 의존성을 가지면 안되기에, 순수한 Kotlin 코드로만 구성되어야 한다
즉, Repository(interface)가 속한 Domain계층에서 LiveData 를 사용하는 것은 구조상 어긋난다
LiveData는 안드로이드 플랫폼의 기능이기 때문에 플랫폼 종속성을 가지고 있고, UI 계층에서 관리되기 때문에,
UI계층이 아니라면 LiveData대신 Flow를 사용해야만 한다
# SharedFlow
StateFlow의 일반화된 버전으로 볼 수 있으며, SharedFlow도 Flow 와 달리 Hot Stream 이다
초기값을 가지지 않고, 여러 설정이 가능하기 때문에 주로 이벤트 처리에 사용할 수 있다
class MyViewModel : ViewModel() {
private val _sharedFlow = MutableSharedFlow<String>(
replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val sharedFlow = _sharedFlow.asSharedFlow()
fun changeSharedFlow(){
viewModelScope.launch {
_sharedFlow.emit("안녕하세요")
}
}
}
sharedFlow은 생성 시에
- replay : 새로운 구독자들에게 이전 이벤트를 몇개 방출할지 지정 (Integer)
- extraBufferCapacity : 추가 버퍼를 몇개 생성할지 지정 (Integer)
- onBufferOverflow : 버퍼 초과시 처리 여부 (DROP_OLDEST = oldest 데이터 drop)
와 같은 설정을 재정의 하여 사용할 수 있다.
emit으로 데이터를 방출하고!!
class MainActivity : AppCompatActivity() {
private val myViewModel : MyViewModel by viewModels()
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 3. SharedFlow
lifecycleScope.launchWhenStarted {
myViewModel.sharedFlow.collect {
Toast.makeText(this@MainActivity, it, Toast.LENGTH_SHORT).show()
}
}
}
}
collect로 방출된 결과를 받아와, 이벤트를 발생시킬 수 있다!!
이렇게하면 MainActivty를 실행할때마다 "안녕하세요"라는 토스트메시지가 갱신되는것을 볼 수 있을것이다
# 참고자료
https://hanyeop.tistory.com/377
[Android] 코루틴 StateFlow, SharedFlow 사용하기 (vs LiveData)
2021.05.13 - [Android/AAC, MVVM] - [Android] Room + LiveData + ViewModel + DataBinding 사용하여 MVVM 패턴 사용하기 [Android] Room + LiveData + ViewModel + DataBinding 사용하여 MVVM 패턴 사용하기 2021.04.19 - [안드로이드/AAC, MVVM]
hanyeop.tistory.com
https://leesmemo.tistory.com/39
코틀린 StateFlow 및 SharedFlow
스트림 콜드 스트림인 Flow와 달리 StateFlow와 SharedFlow는 핫 스트림이다. Flow에서 콜드 스트림이란 collect 시점과 상관 없이 emit 된 모든 값들을 받아오는 것 을 말한다. 핫 스트림이란 collect한 시점
leesmemo.tistory.com
[Android | Kotlin] Coroutine과 Flow 정리하기(4) - State Flow
Live Data와 Flow가 합쳐진 State Flow?!!
velog.io
https://everyday-develop-myself.tistory.com/306
StateFlow, SharedFlow에 대해 알아보기
State란? State는 객체 지향 관점에서 자주 사용되는 단어로 객체가 특정 시점에서 어떤 데이터 값을 가지고 있는지 나타내는 것으로, 객체의 특성이나 속성을 나타냅니다. 안드로이드의 UI 레이어
everyday-develop-myself.tistory.com
[Flow에서 여러 함수들을 사용하여 데이터를 다양하게 처리하는 방법을 보여주는 자료]
https://kotlinlang.org/docs/flow.html
Asynchronous Flow | Kotlin
kotlinlang.org
[공식문서]
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ko
StateFlow 및 SharedFlow | Kotlin | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. StateFlow 및 SharedFlow 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. StateFlow와 SharedFlow는 흐름에서 최적
developer.android.com
'개발 노트 > Kotlin' 카테고리의 다른 글
[Android/Kotlin] Paging3란? (0) | 2024.05.27 |
---|---|
[Android/Kotlin] collect과 collectLatest의 차이점? [Flow] (0) | 2024.05.27 |
[Android/Kotlin] Hilt란? (0) | 2024.05.24 |
JSON 보기편하게 변환 해주는 사이트 (0) | 2024.05.03 |
[Android/Kotlin] Google Map API 사용해서 구글 지도맵 만들기 (0) | 2024.05.01 |