👩💻 오늘의 할 일
서버로에서 가져온 데이터를 기반으로 경도와 위도를 사용해서 지도에 마커를 표시해주고 있습니다. 문제는 처음 지도를 보여주는 Fragment가 생성되면 정상적으로 마커가 표시 되지만 BottomNavigation으로 다른 Fragment로 전환 후 다시 지도로 돌아오면 마커가 표시되지 않는 문제가 발생했습니다.서버의 데이터는 ViewModel에서 StateFlow로 받아왔습니다. 그래서 저는 문제의 원인을 StateFlow로 특정했습니다.
StateFlow를 원인으로 삼은 이유는 StateFlows는 중복된 값을 방출 하지 않는다는 점 때문입니다.
자세한 글은 앞선 포스팅을 참고해주세요!
지난 1월 15일 작성했던 글을 처음으로 StateFlow를 사용했고 SharedFlow를 사용하게 되면 꼭 이에 대한 내용도 작성하려구 했었는데 드디어 오늘이 그날이네요 ! 그 동안 StateFlow 만으로 잘 사용했는데 드디어 사용할 일이 생겼습니다. 그래서 오늘은 SharedFlow에 대해 알아보려구 해요 💪
📑 상태(State)와 이벤트(Event)
StateFlow와 SharedFlow는 각각 State 와 Event를 기반으로 동작합니다.
어플리케이션 개발시 필연적으로 다루게될 상태와 이벤트에 대해 먼저 알아보겠습니다.
상태(State)
어플리케이션에서 상태란, 특정 시점에 객체가 가지고 있는 데이터 또는 어떤 동작을 말합니다. 예를 들어 사용자의 계정 상태는 "로그인", "로그아웃" 으로 특정될 수 있고 사용자는 어플리케이션을 사용하는 시점에서 반드시 두 상태중 한 가지 상태를 가지게 됩니다.
다른 경우는 화면에 표현하기 위한 데이터의 상태입니다. "데이터를 가져오는 상태", "데이터를 사용할 수 있는 상태", "오류" 등으로 정의되며 데이터 역시 이 중 한가지의 상태를 가집니다.
그래서 어플리케이션은 필요한 상태를 정의하고 UI 렌더링, 비즈니스 로직 수행 등의 동작을 하며 상태를 전이해가며 실행됩니다. MVVM 아키턱쳐 관점에서 보면 UI와 관련된 상태, 즉 UI에 렌더링될 데이터 들은 ViewModel에 위치해 ViewModel과 바인딩된 View들이 이 상태(데이터)를 표현합니다.
이벤트(Event)
상태(state)는 항상 기본 동작(상태)를 갖고 동작하지만 이벤트는 기본값 없이 특정 상황이 발생했을 때 구독자들에게 발생한 상황을 이벤트라는 형태로 전달합니다. 이벤트는 보통 버튼 클릭, 알림 수신, 네트워크 요청 완료 등과 같은 사용자의 동작 또는 시스템에서 발생한 상황을 나타냅니다. 로그인 버튼을 클릭하면 "로그인 상태"로의 전환을 유발하는 이벤트가 발생할 수 있고, 이에 따라 어플리케이션의 상태가 변경될 수 있습니다.
📑 StateFlow VS SharedFlow 실습 예제
class MainViewModel: ViewModel() {
private val _stateFlow = MutableStateFlow("Hello World")
val stateFlow = _stateFlow.asStateFlow()
private val _sharedFlow = MutableSharedFlow<String>()
val sharedFlow = _sharedFlow.asSharedFlow()
fun triggerStateFlow() = viewModelScope.launch {
_stateFlow.value = "나는! 나는! StateFlow"
}
fun triggerSharedFlow() = viewModelScope.launch {
viewModelScope.launch {
_sharedFlow.emit("나는! 나는! SharedFlow")
}
}
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.stateFlow.collect {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.sharedFlow.collect {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
}
}
with(binding){
StateFlowButton.setOnClickListener {
viewModel.triggerStateFlow()
}
SharedFlowButton.setOnClickListener{
viewModel.triggerSharedFlow()
}
}
}
}
실행 결과
StateFlow 버튼을 클릭해도 다시 스낵바가 생성되지 않는 것은 StateFlow의 상태가 변하지 않았고 이는 StateFlow가 중복된 값을 emit 하지 않는 다는것을 확인할 수 있습니다. 반면, SharedFlow는 반복해서 스낵바를 생성합니다. 이는 버튼 클릭같이 이벤트 같은 작업에 적합하다는 것을 알 수 있습니다.
📖 SharedFlow 내부 코드
🙇♂️ 후기
제가 생각했던 결과와는 달리 여전히 마커가 표시되지 않습니다. 아무래도 중복된 값을 방출하는 것과는 관계가 없나보네요. 이게 잘 되면 SharedFlow에 replyCache나 다양한 옵션들을 추가해서 실험해보고 싶었는데 당장에 프로젝트 진행이 너무 늦춰지는거 같아 시간이 날 때 한번 해보려 합니다. 원래 프로젝드 엔드 데이를 3월로 했는데 동아리 면접이랑 다른 사정들 때문에 너무 늦춰져서요😢
다른 방법을 생각해 봤는데 Activity에서 ViewModel을 관리하면 어떨까 싶어요! Local에서 데이터 수집은 Fragment들의 Parent Acitivty에서 하고 Chlid Fragment에선 이 수집한 데이터들을 가져와서 쓰기만 하는거죠. SharedFlow를 사용해볼 좋은 기회라고 생각했는데 아쉽네요. 다음장에서 좀 더 자세하게 제가 왜 이 생각을 하게 되었는지 과정과 결과를 한번 보여드리겠습니다.
'KOTLIN' 카테고리의 다른 글
Kotlin Value Class With Project Valhalla (1) | 2024.11.22 |
---|---|
[Kotlin]Sealed Class란 무엇일까 ? (0) | 2024.03.22 |
StateFlow가 중복된 값을 반환하지 않는 이유(DistinctUntilChanged) (0) | 2024.03.08 |
[Kotlin] LiveData 대신 StateFlow 사용하기 (1) | 2024.01.15 |
[Kotlin] Coroutine Flow (0) | 2024.01.15 |