--- id: android-flow-stateflow-sharedflow title: Flow / StateFlow / SharedFlow — 어떤 걸 언제 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [android, kotlin, flow, vibe-coding] tech_stack: { language: "Kotlin Coroutines", applicable_to: ["Android", "Server"] } applied_in: [] aliases: [cold flow, hot flow, replay, state vs event] --- # Flow / StateFlow / SharedFlow > 셋 다 비동기 stream 이지만 의미 다름: **Flow = 1회성 cold**, **StateFlow = 현재 상태 (hot, 1 replay)**, **SharedFlow = 이벤트 버스 (hot, configurable replay)**. 잘못 고르면 이벤트 손실 또는 중복 emit. ## 📖 핵심 개념 - Cold flow: collect 시작할 때 producer 시작. collector 0 = 비활성. 각 collector 별 독립. - Hot flow: producer 가 계속 동작. collector 수와 무관. 다수 collector 공유. - StateFlow: 현재 값 보유. 새 collector → 즉시 현재 값 받음. distinctUntilChanged 자동. - SharedFlow: 이벤트 list. replay 옵션 (새 collector 가 받을 옛 emission 수). ## 💻 코드 패턴 ### Flow — DB / network result ```kotlin fun getUser(id: String): Flow = flow { val u = api.fetch(id) emit(u) } ``` ### StateFlow — UI state ```kotlin class ViewModel : ViewModel() { private val _state = MutableStateFlow(UiState.Idle) val state: StateFlow = _state.asStateFlow() fun load() { viewModelScope.launch { _state.value = UiState.Loading // ... } } } ``` ### SharedFlow — 일회성 이벤트 ```kotlin private val _events = MutableSharedFlow(replay = 0, extraBufferCapacity = 1) val events: SharedFlow = _events.asSharedFlow() fun showError(msg: String) { _events.tryEmit(Event.Error(msg)) } ``` ### stateIn — Flow → StateFlow 변환 ```kotlin val state: StateFlow = repo.observeUser(id) .map { UiState.Success(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), // 5s 후 unsubscribe initialValue = UiState.Loading, ) ``` ### combine, flatMapLatest, debounce ```kotlin val results = combine(query.debounce(300), filters) { q, f -> q to f } .flatMapLatest { (q, f) -> repo.search(q, f) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) ``` ## 🤔 의사결정 기준 | 데이터 | 권장 | |---|---| | 1회성 fetch (사용자 정보, network call) | Flow | | UI 가 항상 봐야 할 현재 state | StateFlow | | Snackbar / Navigation 같은 일회성 이벤트 | SharedFlow (replay=0) | | 다수 구독자 + buffered | SharedFlow + replay/buffer | | Database (Room flow) | Flow → stateIn | | 화면 회전 시 이벤트 재실행 막기 | SharedFlow + STARTED 만 collect | ## ❌ 안티패턴 - **이벤트 (Snackbar) 를 StateFlow 로**: state 라 회전 시 재실행. SharedFlow + replay=0. - **state 를 SharedFlow 로**: 새 collector 가 옛 값 못 받음 (replay=0). StateFlow 가 맞음. - **stateIn 없이 cold flow 를 UI 에서 collect**: 회전 시 새 collect = 새 fetch. stateIn(WhileSubscribed) 권장. - **collectLatest 대신 collect** (검색 같은 케이스): 이전 결과가 늦게 도착해 새 결과 덮어씀. flatMapLatest / collectLatest. - **MutableStateFlow 를 외부 노출**: 외부 mutate 가능. asStateFlow(). - **emit 대신 tryEmit 인데 buffer 0**: drop. 버퍼 또는 emit (suspend). ## 🤖 LLM 활용 힌트 - "현재 상태 = StateFlow, 일회성 이벤트 = SharedFlow(replay=0), Room/network = Flow + stateIn" 명시. - WhileSubscribed(5000) 이 표준. ## 🔗 관련 문서 - [[Android_Kotlin_Coroutines_Scopes]] - [[Android_ViewModel_State_Persistence]]