저는 이번에 전체적으로 clean Arcitechture 를 도입하면서 flow를 정리하였습니다. liveData를 flow로 옮겨야 할지에 대해 고민을 하였습니다. livedata와 flow 둘 다 모두 비동기 데이터 스트림을 처리하는 데 사용하는 것으로써 사용방법에는 차이가 있습니다. flow를 livedata처럼 사용하기 위해서는 sharedFlow혹은 stateFlow를 사용하는데요. 해당 방법의 차이점과 사용방법을 생각해보고 sharedflow
와 stateflow
의 차이점에 대해서도 간략히 정리해보겠습니다.
stateflow는 이전 값과 비교해서 값이 변경이 되는 마치 liveData와 정말 흡사하게 작동됩니다. 외부에서 상태 변경이 발생되어 그 부분을 가지고 해당 내용의 변경사항이 반영되어야 할때 사용합니다.
...
class MainViewModel : ViewModel() {
val stateFlow = MutableStateFlow(0)
init {
viewModelScope.launch {
repeat(100000) {
stateFlow.emit(it)
delay(1000)
}
}
}
}
...
10만번가량 emit() 되는 데이터를 받기 위해서 repeatOnLifeCycle
을 사용하여 collect할 수 있게 설계되었습니다. Lifecycle.State.STARTED
로 설계되어있으면 해당 activity가 STARTED 상태일때만 작동이 되어 반복되는 형태를 가집니다.
override void onCreate(savedInstanceState) {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.stateFlow.collect { number ->
println("Collected $number from stateFlow")
}
}
}
}
Sharedflow는 Stateflow와는 다르게 이전 값과 비교해서 값이 변경되는 것이 아니라, 새로운 값을 emit() 하는 것에 목적 이있습니다. 그렇기 때문에 특정한 트리거와 같은 이벤트가 발생될때 주로 사용됩니다. 예를 들면 페이지 사용 시간이 10분이상이 되었을때 발생되는 특정 이벤트나 혹은 snackbar와 같은 데이터가 출력이 되어야 할때 사용합니다.
1초에 한번씩 반영이 되어야 하는 데이터들에 대하여 emit()을 하였습니다
class MainViewModel : ViewModel() {
// 1. SharedFlow
private val _sharedFlow = MutableSharedFlow<Int>();
val sharedFlow = _sharedFlow.asSharedFlow();
init {
viewModelScope.launch {
repeat(100000) {
sharedFlow.emit(it)
delay(1000)
}
}
}
}
emit()되는 방법도 stateflow와 흡사합니다.
override void onCreate(savedInstanceState) {
super.onCreate(savedInstanceState);
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewmodel.sharedFlow.collect { number ->
println("Collected $number from sharedFlow")
}
}
}
}
stateflow와 sharedflow가 다른점은 목적성 뿐만이 아닌 다른 점을 가지고 있습니다. 저는 Android developer 사이트에서 말하는 구독자가 여러개를 가질 수 있다고 sharedflow에 대해 이해하기 어려웠기 때문에 정리해보겠습니다.
구독자가 여러개 라는 뜻은 collect (데이터를 받을 수 있는 곳)가 한개가 아닐 수 있음을 말합니다.
그래서 emit을 한군데에서만 하는것이 아니라 여러 곳에서 emit 을 처리할 수 있습니다. 버튼을 클릭시 여러가지의 리턴을 방출하는 작업을 예시로 들어보겠습니다.
private val _eventFlow = MutableSharedFlow<UiEvent>()
val eventFlow = _eventFlow.asSharedFlow()
...
fun onEvent(event: VideoViewEvent) {
when (event) {
is VideoViewEvent.AddMovieClip -> {
_eventFlow.emit(UiEvent.save)
}
is VideoViewEvent.DeleteMovieClip -> {
_eventFlow.emit(UiEvent.delete)
}
...
}
}
SharedFlow는 값을 방출하기 전에 다수의 구독자가 해당 값을 받을 수 있도록 함으로써 여러가지 형태의 새로운 flow를 반환하게 될 것입니다.
이것은 sharedflow가 가지는 매개변수 replay
가 가지는 기능 입니다.
val chatMessages = MutableSharedFlow<String>(replay = 100)
예를 들어서 만약 하나의 채팅방에서 채팅을 하다 백그라운드로 들어와서 activity의 상태가 변경되었을 경우 해당 emit을 다시 가져오는 기능을 하는 것입니다. 위에 예제에서는 100개의 최근 메시지를 저장합니다. stateflow에서는 당연히 최신 상태만을 중요시 하기 때문에 이런 정보는 갖고 있지 않습니다.
channel에서도 이 기능을 자체적으로 제공하고 있습니다. 다만 channel의 경우에는 하나의 subscriber만 가지고 있습니다! 이 부분은 제가 포스팅 했던 내용을 참고하세요.
일단 flow는 kotlin 라이브러리 입니다. livedata는 jetpack에서 제공하고 있는 lifecycle 하위에 있는 api 로써 사용방법이 전혀 다릅니다.
그리고 저는 kotlin base 로 작성되기 때문에 kotlin으로 사용되는 비동기 코드가 더욱 빠르리라고 예상했지만 제 생각과는 조금 달랐습니다. 정말 미세하지만 적어도 단일개의 collect을 가진 안드로이드 어플리케이션에서는 livedata가 훨씬 빠른 데이터를 보여주고 있는데요
이것 또한 사용 목적이 다르기 때문입니다. 이것을 livedata 의 document에서 flow를 언급합니다
앱의 다른 레이어에서 데이터 스트림을 사용해야 한다면
Kotlin Flow를 사용한 다음 asLiveData()를 사용하여
ViewModel의 LiveData로 변환하는 것이 좋습니다.
livedata는 현재 런타임에 위치하고 있는 android application의 lifecycle을 자체적으로 감지할 수 있기 때문에 UI의 경우에는 mainthread를 사용하여야 하기 때문에 lifecyle을 자체적으로 감지 수 있는 livedata를 선호하는것이 아닌가 싶습니다.