Compose Coroutines, Flow, and State in Compose

Compose + Flow/Coroutines Deep Dive

1. LaunchedEffect와 Coroutine Scope

LaunchedEffect는 Composable scope에 묶인 coroutine을 실행한다.
LaunchedEffect launches a coroutine tied to the composable’s lifecycle.

LaunchedEffect(key1 = userId) {
    viewModel.loadUser(userId)
}
  • key가 바뀌면 기존 coroutine cancel + 새 coroutine 시작
  • key가 Unit이면 최초 한 번만 실행

2. rememberCoroutineScope

val scope = rememberCoroutineScope()

Button(onClick = {
    scope.launch {
        viewModel.doAction()
    }
}) { Text("Action") }
  • Composition lifecycle에 묶인 scope
  • UI 이벤트 + suspend 함수 연결에 필수

3. snapshotFlow

Compose state를 Flow로 변환하는 API.
Transforms Compose state reads into a Flow.

val query by remember { mutableStateOf("") }

LaunchedEffect(Unit) {
    snapshotFlow { query }
        .collect { q -> println("Query: $q") }
}
  • snapshotFlow { } block 내에서 읽은 상태가 변경될 때마다 emit
  • state → Flow 브리지 역할

4. Flow 수집 패턴 (collectAsState, collectAsStateWithLifecycle)

val uiState by viewModel.uiState.collectAsState()
  • StateFlow/Flow를 Compose state로 노출
  • recomposition-friendly

Lifecycle-aware 버전은 collectAsStateWithLifecycle() 사용.


5. 동시성 고려사항 (Concurrency Considerations)

  • 같은 state를 여러 coroutine이 갱신할 때 race condition 주의
  • Snapshot system은 thread-safe지만 “로직 수준” 경쟁은 직접 처리해야 함
  • viewModelScope + immutable UI state 패턴 권장

6. Flow + CompositionLocal 조합

val themeConfig = LocalThemeConfig.current

LaunchedEffect(themeConfig) {
    themeConfig.events.collect { event ->
        // theme-related side effect
    }
}

CompositionLocal 값이 변하면 새로운 Flow 수집이 시작될 수 있음.