StateFlow: 채널의 '실시간 구독자 수' 표시판
StateFlow를 가장 쉽게 이해하는 방법은 바로 유튜브 채널의 '실시간 구독자 수' 표시판을 떠올리는 것입니다. 이 표시판, 어떤 특징이 있을까요?
StateFlow의 핵심 특징
1. 항상 최신 '상태'를 유지해요: 구독자 수 표시판은 절대 비어있지 않죠? 채널을 막 열었을 땐 '0'이라는 초기값이 있고, 이후에는 항상 최신 구독자 수를 보여줍니다. StateFlow도 마찬가지로 반드시 초기값을 가져야 하며, 항상 마지막으로 업데이트된 최신 값(상태) 하나만을 저장하고 있습니다. 과거의 값('99명')은 잊어버리고 현재 값('100명')만 기억하는 거죠.
2. 누구든 현재 값을 바로 볼 수 있어요: 페이지를 새로고침해도 현재 구독자 수를 바로 볼 수 있듯이, 새로운 구독자(collect
)가 생기면 StateFlow는 기다렸다는 듯이 자신이 가진 최신 값을 즉시 전달해줍니다. 또한 .value
프로퍼티를 통해 언제든지 현재 상태 값을 직접 확인할 수도 있습니다.
3. 구독자가 없어도 혼자 존재해요: 구독자 수 표시판은 누가 보든 안 보든 항상 그 자리에 있죠. 이처럼 StateFlow는 구독자가 있든 없든 혼자서 상태를 유지하는 '뜨거운(Hot)' 스트림입니다.
이러한 특징 덕분에 StateFlow는 안드로이드 화면(UI)의 '상태'를 관리하는 데 최고의 도구로 꼽힙니다. 로딩 상태, API 결과 데이터, 에러 상태 등 화면이 항상 알고 있어야 하는 현재 상태를 담아두기에 완벽하죠.
// ViewModel에서 UI 상태를 관리
class MyViewModel : ViewModel() {
// 구독자 수 표시판을 만듭니다. 초기값은 "로딩 중..."
private val _uiState = MutableStateFlow<String>("로딩 중...")
val uiState: StateFlow<String> = _uiState // UI에서는 읽기만 가능하도록 공개
fun fetchData() {
_uiState.value = "데이터 로딩 성공! (구독자 100명 달성!)"
// 값을 .value 로 업데이트하면, 이 StateFlow를 구독하는 모든 곳에
// 새로운 상태가 즉시 전달됩니다.
}
}
SharedFlow: 유튜버의 '실시간 라이브 방송 알림'
이번엔 SharedFlow를 알아볼까요? SharedFlow는 마치 유튜버가 라이브 방송을 시작할 때 모든 구독자에게 일제히 보내는 '라이브 시작!' 알림과 같습니다.
SharedFlow의 핵심 특징
1. '상태'가 아닌 '이벤트'를 전달해요: '라이브 시작!' 알림은 구독자 수처럼 계속 유지되는 값이 아니라, '지금 방송 켰어요!'라고 알리는 일회성 사건(Event)입니다. SharedFlow는 이처럼 딱 한 번만 처리되어야 하는 이벤트를 전달하는 데 특화되어 있습니다.
2. 늦게 오면 놓칠 수 있어요: 알림이 울린 뒤 한참 후에 앱을 켜면 그 알림을 못 볼 수도 있죠? SharedFlow도 기본적으로 이벤트가 발생한 순간에 구독하고 있던 구독자들에게만 내용을 전달합니다. 과거의 이벤트는 그냥 지나가 버리죠. (물론 replay
옵션으로 최근 이벤트 몇 개를 저장해뒀다가 새로운 구독자에게 보여줄 수도 있습니다.)
3. 모두에게 공유돼요: '라이브 시작!' 알림이 모든 구독자에게 동시에 전달되듯, SharedFlow는 이벤트를 모든 구독자에게 공유(Share)합니다. 이 역시 구독자가 있든 없든 이벤트를 발생시킬 수 있는 '뜨거운(Hot)' 스트림입니다.
그래서 SharedFlow는 "저장이 완료되었으니 토스트 메시지를 한 번만 띄워줘", "버튼을 누르면 다음 화면으로 딱 한 번만 이동해줘" 와 같이 한 번만 수행되어야 하는 이벤트를 ViewModel에서 UI로 전달할 때 빛을 발합니다. 화면이 회전되더라도 이벤트가 중복 실행되는 골치 아픈 문제를 막아주죠.
class MyViewModel : ViewModel() {
// 라이브 방송 알림 채널을 만듭니다.
private val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _events
fun onSaveButtonClick() {
// ... 저장 로직 ...
viewModelScope.launch {
_events.emit("저장되었습니다!") // 알림 발송!
}
}
}
// UI(Fragment/Activity)에서
viewModel.events.collect { message ->
// 이 코드는 화면 회전 시 다시 실행되지 않아요!
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
최종 정리
구분 | Flow (유튜브 채널) | StateFlow (구독자 수) | SharedFlow (라이브 방송 알림) |
---|---|---|---|
스트림 종류 | Cold (구독자가 생길 때마다 1:1로 새로 시작) | Hot (구독자 여부와 관계없이 항상 활성) | Hot (구독자 여부와 관계없이 항상 활성) |
핵심 목적 | 일반적인 비동기 데이터 스트림 | 상태(State) 관리 (화면의 현재 상태) | 이벤트(Event) 전달 (일회성 알림) |
초기값 | 없음 | 필수 (항상 값이 있어야 함) | 없음 |
새 구독자 | 처음부터 모든 데이터를 순서대로 받음 | 구독 즉시 가장 최신 상태 값 1개를 받음 | 기본적으로 아무것도 받지 못함 (과거 이벤트는 놓침) |
주요 사용처 | Room, Retrofit 등 라이브러리와의 연동 | UI 상태 관리 (화면 데이터, 로딩 상태 등) | UI 이벤트 전달 (토스트 메시지, 화면 이동 등) |