안드로이드 코틀린 언어 학습 노트 목차

Flow·StateFlow·SharedFlow — 비동기 데이터 스트림

코루틴의 suspend값 하나를 비동기로 돌려준다면, Flow여러 값을 시간차로 흘려보내는 비동기 스트림이다 — 검색어 입력, DB 변경 구독, 위치 업데이트처럼 연속해서 도착하는 데이터에 쓴다. 안드로이드의 *상태(StateFlow)·이벤트(SharedFlow)*가 모두 Flow 위에 서 있어, MVVM·단방향 데이터 흐름의 핵심 배관이다. (출처: Kotlin Docs — Flow / Android — StateFlow & SharedFlow.)

Flow 기본 — cold 스트림

flow { } 빌더 안에서 emit()으로 값을 방출하고, collect(suspend 최종 연산)로 받는다. Flow는 coldcollect할 때 비로소 실행되고, 구독자마다 처음부터 새로 흐른다.

fun numbers(): Flow<Int> = flow {
    for (i in 1..3) { delay(100); emit(i) }   // 값을 하나씩 방출
}

scope.launch {
    numbers().collect { value ->              // collect해야 흐르기 시작 (cold)
        println(value)                        // 1, 2, 3 (시간차)
    }
}

연산자 — 코루틴 위의 함수형 파이프라인

Flow도 컬렉션처럼 연산자를 체이닝한다. 단, 모두 코루틴 안에서 비동기로 돈다.

searchQuery
    .debounce(300)                    // 입력이 멎고 300ms 후에만 (타이핑 폭주 억제)
    .filter { it.length >= 2 }
    .distinctUntilChanged()           // 같은 값 연속이면 무시
    .flatMapLatest { q ->             // 새 검색어 오면 이전 검색 취소하고 최신만
        repository.search(q)          // Flow 반환
    }
    .flowOn(Dispatchers.IO)           // 위(upstream) 연산을 IO에서
    .catch { e -> emit(emptyList()) } // 예외를 스트림 안에서 처리
    .collect { results -> render(results) }

flowOn위쪽(upstream) 연산의 디스패처를 바꾸고(아래는 collect한 곳), catch상류 예외를 잡으며, combine/zip여러 Flow를 합친다. debounce·flatMapLatest검색·입력 처리의 교과서 조합이다.

cold vs hot — Flow와 StateFlow/SharedFlow

다이어그램 로딩 중…

기본 Flow는 cold(구독해야 시작, 구독자마다 독립)지만, StateFlow·SharedFlow는 hot구독자와 무관히 존재하고 여러 구독자가 같은 방출을 공유한다. 그래서 화면 상태·이벤트에 적합하다.

StateFlow — 화면 '상태'의 단일 출처

StateFlow항상 현재값 하나를 들고 있는 hot Flow다(상태 홀더). value즉시 현재값을 읽고, 값이 바뀌면 구독자에게 흘린다. 같은 값은 방출 안 함(conflated·distinct). MVVM에서 ViewModel이 화면 상태를 StateFlow로 노출하는 게 표준이다.

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()   // 밖엔 읽기 전용

    fun load() = viewModelScope.launch {
        _uiState.value = UiState.Loading
        runCatching { repo.load() }
            .onSuccess { _uiState.value = UiState.Success(it) }
            .onFailure { _uiState.value = UiState.Error(it.message ?: "") }
    }
}

StateFlow vs LiveData — 둘 다 생명주기 인지 관찰 가능한 상태 홀더지만, StateFlow는 코루틴/Flow 생태계에 통합되고 초기값이 필수이며 플랫폼 독립(순수 코틀린)이다. 구글은 신규 코드에 StateFlow를 권장한다.

SharedFlow — 일회성 '이벤트'

화면 이동·스낵바·토스트 같은 일회성 이벤트는 StateFlow(상태)로 표현하면 회전 후 재방출 등 문제가 생긴다. SharedFlow상태가 아닌 이벤트 스트림으로, replay/buffer를 조절해 여러 구독자에게 방출한다.

private val _events = MutableSharedFlow<UiEvent>()       // replay=0: 새 구독자에 과거 X
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun onSaved() = viewModelScope.launch { _events.emit(UiEvent.NavigateBack) }
StateFlowSharedFlow
성격상태(현재값 1개)이벤트 스트림
초기값필수없음
중복conflate(같은 값 무시)설정대로 방출
용도UI 상태네비게이션·토스트

안드로이드에서 안전하게 수집

Flow를 화면에서 수집할 땐 생명주기에 맞춰야 한다 — 백그라운드에서도 수집하면 낭비·크래시다. repeatOnLifecycle(STARTED)화면이 보일 때만 수집하고 안 보이면 취소한다([andsys] 코루틴 on Android에서 자세히).

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { render(it) }   // STARTED일 때만 수집
    }
}

stateIn/shareIn으로 cold Flow를 hot StateFlow/SharedFlow로 변환해 여러 구독자가 공유하게 만든다(SharingStarted.WhileSubscribed).

정리 — 스트림으로 상태와 이벤트를 흘린다

Flow의 핵심 — 여러 비동기 값을 흘리는 cold 스트림(연산자로 변환), StateFlow는 현재값 하나를 들고 UI 상태를 단일 출처로, SharedFlow는 일회성 이벤트. 안드로이드에선 ViewModel이 StateFlow로 상태를 노출하고, 화면은 repeatOnLifecycle로 안전하게 수집한다. 이 배관이 단방향 데이터 흐름(UDF)([andarch])을 가능케 한다.

한 줄 요약 — Flow=여러 값 비동기 스트림(cold, collect해야 흐름). 연산자 map/debounce/flatMapLatest/flowOn/catch. Hot: StateFlow(현재값 1개·UI 상태·초기값 필수·conflate) vs SharedFlow(이벤트·replay 조절). 수집은 repeatOnLifecycle(STARTED)로 생명주기 안전, stateIn으로 cold→hot.

(출처: Kotlin Flow / Android — StateFlow/SharedFlow / Collect safely / Kotlin in Action 2e Ch 16.)

코루틴 — suspend·구조적 동시성·Dispatchers·취소제네릭·변성 — in/out·스타 프로젝션·타입 소거