수명 주기 인식 구성요소로 Kotlin 코루틴 사용

Kotlin 코루틴은 비동기 코드를 작성할 수 있는 API를 제공합니다. Kotlin 코루틴을 사용하면 코루틴이 실행되어야 하는 시기를 관리하는 데 도움이 되는 CoroutineScope를 정의할 수 있습니다. 각 비동기 작업은 특정 범위 내에서 실행됩니다.

수명 주기 인식 구성요소는 앱의 논리적 범위에 관한 코루틴을 가장 잘 지원합니다. 이 문서에서는 수명 주기 인식 구성요소와 함께 코루틴을 효과적으로 사용하는 방법을 설명합니다.

종속 항목 추가

본 항목에서 설명하는 기본 제공 코루틴 범위는 Lifecycle API에 포함되어 있습니다. 이러한 범위 사용 시 적절한 종속 항목을 추가해야 합니다.

  • Compose의 ViewModel 유틸리티에는 implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")를 사용하세요.
  • Compose의 수명 주기 유틸리티는 implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version")를 사용하세요.

수명 주기 인식 코루틴 범위

Compose 및 Lifecycle 라이브러리는 앱에서 사용할 수 있는 다음 기본 제공 범위를 제공합니다.

ViewModelScope

ViewModelScope는 앱의 각 ViewModel을 대상으로 정의됩니다. 이 범위에서 시작된 모든 코루틴은 ViewModel이 삭제되면 자동으로 취소됩니다. 코루틴은 ViewModel이 활성 상태인 경우에만 실행해야 할 작업이 있을 때 유용합니다. 예를 들어 레이아웃의 일부 데이터를 계산한다면 작업의 범위를 ViewModel로 지정하여 ViewModel을 삭제하면 리소스를 소모하지 않도록 작업이 자동으로 취소됩니다.

다음 예와 같이 ViewModelviewModelScope 속성을 통해 ViewModelCoroutineScope에 액세스할 수 있습니다.

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

고급 사용 사례의 경우 맞춤 CoroutineScope를 ViewModel의 생성자에 직접 전달하여 기본 viewModelScope를 대체할 수 있습니다. 이 접근 방식은 특히 다음의 경우 더 많은 제어 기능과 유연성을 제공합니다.

  • 테스트: TestScope를 삽입하여 단위 테스트에서 시간을 더 쉽게 제어하고 코루틴 동작을 확인할 수 있습니다.

  • 맞춤 구성: ViewModel이 작업을 시작하기 전에 특정 CoroutineDispatcher (예: 과도한 계산의 경우 Dispatchers.Default) 또는 맞춤 CoroutineExceptionHandler로 범위를 구성할 수 있습니다.

컴포지션 바운드 범위

애니메이션, 네트워크 호출, 타이머와 같은 부작용은 컴포저블의 수명 주기로 범위가 지정되어야 합니다. 이렇게 하면 컴포저블이 화면을 벗어나면(컴포지션을 종료하면) 실행 중인 코루틴이 자동으로 취소되어 메모리 누수를 방지할 수 있습니다.

Compose는 Composition 범위 지정을 선언적으로 처리하는 LaunchedEffect API를 제공합니다.

LaunchedEffect는 suspend 함수를 실행할 수 있는 CoroutineScope를 만듭니다. 범위는 호스트 활동의 수명 주기가 아닌 컴포저블의 컴포지션 수명 주기에 연결됩니다.

  • 진입: 컴포저블이 컴포지션에 진입하면 코루틴이 시작됩니다.
  • 종료: 컴포저블이 컴포지션을 종료하면 코루틴이 취소됩니다.
  • 다시 실행: LaunchedEffect에 전달된 키가 변경되면 기존 코루틴이 취소되고 새 코루틴이 실행됩니다.

다음 예에서는 LaunchedEffect를 사용하여 맥동 애니메이션을 만드는 방법을 보여줍니다. 코루틴은 컴포지션에 컴포저블이 있는지 여부에 연결되어 있으며 구성 변경에 반응합니다.

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

LaunchedEffect에 관한 자세한 내용은 Compose의 부수 효과를 참고하세요.

수명 주기 인식 흐름 수집

Jetpack Compose에서 흐름을 안전하게 수집하려면 collectAsStateWithLifecycle API를 사용하세요. 이 단일 함수는 Flow를 Compose State 객체로 변환하고 수명 주기 구독을 자동으로 관리합니다. 기본적으로 수집은 수명 주기가 STARTED일 때 시작되고 수명 주기가 STOPPED일 때 중지됩니다. 이 기본 동작을 재정의하려면 원하는 수명 주기 메서드(예: Lifecycle.State.RESUMED)와 함께 minActiveState 매개변수를 전달하세요.

다음 예에서는 컴포저블에서 ViewModel의 StateFlow를 수집하는 방법을 보여줍니다.

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

여러 흐름의 병렬 수집

Compose에서는 여러 상태 변수를 선언하여 여러 흐름을 동시에 수집할 수 있습니다. collectAsStateWithLifecycle는 자체 기본 범위를 관리하므로 병렬 수집이 자동으로 처리됩니다.

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

흐름을 사용하여 비동기적으로 값 계산

값을 비동기적으로 계산해야 하는 경우 stateIn 연산자와 함께 StateFlow를 사용합니다.

다음 스니펫은 StateFlow로 변환된 표준 Flow를 사용합니다. WhileSubscribed(5000) 매개변수는 구성 변경을 처리하기 위해 UI가 사라진 후 5초 동안 구독을 활성 상태로 유지합니다.

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

수집된 값을 Compose State로 변환하여 데이터가 변경될 때마다 UI가 반응형으로 업데이트되도록 collectAsStateWithLifecycle를 사용합니다.

상태에 관한 자세한 내용은 상태 및 Jetpack Compose를 참고하세요.

추가 리소스

콘텐츠 조회

샘플