One-Time Event(일회성 이벤트)란, 안드로이드 UI에서 단 한 번만 처리되어야 하는 이벤트를 말합니다.
대표적인 예시로는 다음과 같은 것들이 있습니다:
이러한 이벤트들은 한 번만 처리되어야 함에도 불구하고, 안드로이드의 라이프사이클이나 화면 회전 등의 설정 변경(configuration changes)으로 인해 같은 이벤트가 다시 처리되는 문제가 발생할 수 있습니다.
Compose에서 일회성 이벤트를 안전하게 처리하기 위해 아래와 같은 유틸리티 컴포저블을 만들 수 있습니다:
@Composable
fun <T> ObserveAsEvents(
flow: Flow<T>,
key1: Any? = null,
key2: Any? = null,
onEvent: (T) -> Unit
) {
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(lifecycleOwner.lifecycle, key1, key2) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
withContext(Dispatchers.Main.immediate) {
flow.collect(onEvent)
}
}
}
}
LocalLifecycleOwner.current
→ 현재 컴포저블의 라이프사이클을 추적합니다.
이를 통해 UI가 포그라운드 상태일 때만 이벤트를 수신하도록 할 수 있습니다.
LaunchedEffect + 키 값(key1, key2)
→ LaunchedEffect는 키 값이 변경될 때마다 블록을 재시작합니다.
이로써 동적으로 수집 조건을 제어할 수 있습니다.
repeatOnLifecycle(Lifecycle.State.STARTED)
→ 해당 블록은 Lifecycle이 STARTED 상태일 때만 작동하며, DESTROYED 또는 CREATED 상태에서는 자동으로 정지(suspend)됩니다.
라이프사이클이 STARTED 이전 상태(CREATED, INITIALIZED)이거나 DESTROYED 상태인 경우에는 collect가 작동하지 않습니다.
따라서 이 시점에 ViewModel에서 이벤트가 emit되면 UI는 이를 수신하지 못하고 이벤트가 유실될 수 있습니다.
예:
이 문제를 방지하기 위해 collect를 다음과 같이 실행합니다:
withContext(Dispatchers.Main.immediate) {
flow.collect(onEvent)
}
현재 메인 스레드에 있다면 즉시 실행하고,
그렇지 않다면 메인 큐에 가장 앞줄로 추가하여 지연 없이 실행하기 때문입니다.
이렇게 하면 Lifecycle 상태 전환 사이의 미묘한 시점에서 발생한 이벤트도 놓치지 않고 수집할 수 있습니다.