반응형
안녕하세요. Jamie입니다.
이번 포스팅은 Flow에 대해서 쉽게 이해할 수 있도록 정리해보았습니다.
Flow는 현재 Android 앱 개발에서는 Coroutine과 함께 비동기 + 스트림 처리에는 필수적으로 사용되는 스택입니다.
우선 Flow에는 크게 3대 구성요소가 있습니다.
Flow의 3대 구성요소
- 생산자 (Producer)
- 중간 연산자(Intermediary)
- 소비자 (Consumer)
이 요소들은 다음과 같은 특징이 있습니다.
- Producer (생산자): 데이터를 스트림에 흘려보냅니다.
- flow { ... } 블록 안에서 emit()을 통해 데이터를 전달합니다. (예: Repository 레이어의 DB 쿼리 결과)
- Intermediary (중개자, 중간 연산자): 흘러가는 데이터를 가공합니다.
- map, filter, take, zip 등 익숙한 연산자들이 여기 해당하며, 코루틴 위에서 동작하므로 이 안에서 다른 suspend 함수를 호출할 수 있습니다.
- Consumer (소비자): 데이터를 최종적으로 받아서 처리합니다.
- collect를 호출하는 시점에 비로소 Flow가 동작하기 시작합니다. (예: ViewModel이나 UI 레이어)
간단한 예시를 통해 이해하기 쉽게 설명하겠습니다.
// 1. 생산자 (Producer): 1초마다 숫자를 하나씩 내보냄
val numberFlow: Flow<Int> = flow {
for (i in 1..5) {
delay(1000)
emit(i) // 데이터를 스트림에 흘려보냄
}
}
// 2. 중개자 (Intermediary): 짝수만 골라내고 값을 10배로 뻥튀기
val transformedFlow = numberFlow
.filter { it % 2 == 0 } // 짝수만 통과 (필터)
.map { it * 10 } // 값 변환 (가공)
// 3. 소비자 (Consumer): 변환된 데이터를 받아서 UI에 출력
suspend fun displayNumbers() {
transformedFlow.collect { value ->
println("최종 받은 값: $value") // 소비 (사용)
}
}
코드를 보고 설명하자면 생산자, 중개자, 소비자의 개념을 아래와 같이 정리할 수 있습니다.
- 생산자: 데이터를 만들어 emit 합니다. 보통 Repository 레이어에서 서버나 DB 데이터를 가져올 때 담당합니다.
- 중개자: 데이터를 소비하기 전, 입맛에 맞게 수정합니다. (Optional, 생략 가능)
- 소비자: collect를 호출하여 데이터를 실제로 사용합니다. 주로 UI(Activity/Fragment)나 ViewModel에서 이 역할을 수행합니다.
자 Flow의 3대 구성 요소를 설명했으니 이제 조금 더 중요한 부분에 대해 설명하겠습니다.
Flow는 기본적으로 Stream 데이터를 2가지로 구분하게 됩니다.
Cold Stream vs Hot Stream
Cold Flow (flow { ... })
- 특징: 소비자가 collect를 호출할 때마다 생산자가 새로 시작됩니다.
- 비유: CD 플레이어. 내가 재생 버튼(collect)을 눌러야만 음악이 처음부터 나옵니다.
- 용도: 데이터베이스 쿼리, 네트워크 요청 등 일회성 스트림.
Hot Flow (StateFlow, SharedFlow)
- 특징: 소비자가 있든 없든 데이터를 계속 생산하고 있거나, 상태를 유지합니다. 여러 소비자가 하나의 스트림을 공유할 수 있습니다.
- 비유: 라디오 방송. 내가 언제 라디오를 켜든(collect) 현재 방송 중인 내용을 듣게 됩니다.
- 용도: UI 상태 관리(StateFlow), 클릭 이벤트나 푸시 알림(SharedFlow).
그림으로 쉽게 설명해보겠습니다. Gemini 나노바나나로 제작했습니다.

StateFlow와 SharedFlow의 예제 코드입니다.
class MainViewModel : ViewModel() {
// 1. StateFlow: UI 상태 관리 (초기값 필수)
// 화면을 돌리거나 다시 들어와도 마지막 상태를 유지하고 보여줌
private val _uiState = MutableStateFlow(MainUiState())
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
// 2. SharedFlow: 일회성 이벤트 관리
// 관찰자가 있을 때만 전달, 처리 후 재발생하지 않음 (이벤트 유실 주의 전략 필요)
private val _eventFlow = MutableSharedFlow<MainEvent>()
val eventFlow: SharedFlow<MainEvent> = _eventFlow.asSharedFlow()
// 상태 변경 로직
fun incrementCounter() {
_uiState.update { it.copy(count = it.count + 1) }
}
// 이벤트 발생 로직
fun showToast() {
viewModelScope.launch {
_eventFlow.emit(MainEvent.ShowSnackbar("데이터가 업데이트되었습니다!"))
}
}
}
// 상태 데이터 클래스
data class MainUiState(
val count: Int = 0,
val isLoading: Boolean = false
)
// 이벤트 정의 (Sealed Interface로 깔끔하게)
sealed interface MainEvent {
data class ShowSnackbar(val message: String) : MainEvent
object NavigateToDetail : MainEvent
}
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
val context = LocalContext.current
// StateFlow 수집: UI 상태는 Lifecycle을 따르며 항상 최신 값을 유지함
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// SharedFlow 수집: 일회성 이벤트는 LaunchedEffect 내에서 처리
LaunchedEffect(Unit) {
viewModel.eventFlow.collect { event ->
when (event) {
is MainEvent.ShowSnackbar -> {
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
}
MainEvent.NavigateToDetail -> { /* 화면 이동 로직 */ }
}
}
}
// UI 레이아웃
Column {
Text(text = "현재 카운트: ${uiState.count}")
Button(onClick = { viewModel.incrementCounter() }) {
Text("카운트 증가")
}
Button(onClick = { viewModel.showToast() }) {
Text("토스트 띄우기")
}
}
}
StateFlow와 SharedFlow를 정리하자면 다음과 같습니다.
| 비교 포인트 | StateFlow | SharedFlow |
| 성격 | State (상태) | Event (이벤트) |
| 초기값 | 필수 (initialValue) | 없음 |
| Sticky 기능 | 항상 마지막 값을 들고 있음 (새 구독자에게 즉시 전달) | 기본적으로 없음 (설정에 따라 replay 가능) |
| 중단 시점 | UI가 멈춰도 상태를 유지함 | 구독자가 없으면 이벤트를 버릴 수 있음 (설정 가능) |
| 주요 활용 | 텍스트 필드 값, 리스트 데이터, 로딩 상태 | 스낵바, 토스트, 화면 이동, 앱 종료 등 |
반응형
'Development > Android' 카테고리의 다른 글
| Android Studio Git 에서 Staged/Unstaged 사용하기 (0) | 2026.04.15 |
|---|---|
| Koin? 한번 알아볼까요? (0) | 2026.04.01 |
| Android LiveData에서 Flow로 옮겨가야하는 이유 (0) | 2026.03.31 |
| libs.versions.toml 써보셨나요? (0) | 2026.03.22 |
| Android Studio New UI 적용하기 (0) | 2023.08.18 |