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

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

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

종속 항목 추가

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

  • Compose의 ViewModel 유틸리티에는 implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")를 사용합니다.
  • Compose의 Lifecycle 유틸리티에는 implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version")를 사용합니다.

수명 주기 인식 코루틴 범위

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

ViewModelScope

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

다음 예시와 같이 ViewModel의 viewModelScope 속성을 통해 ViewModel의 CoroutineScope에 액세스할 수 있습니다.

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는 컴포지션 범위를 선언적으로 처리하는 LaunchedEffect API를 제공합니다.

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

  • 시작: 컴포저블이 컴포지션을 시작할 때 코루틴이 시작됩니다.
  • 종료: 컴포저블이 컴포지션을 종료하면 코루틴이 취소됩니다.
  • 다시 실행: 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()

    // ...
}

Flow를 사용하여 비동기식으로 값 계산

값을 비동기식으로 계산해야 하는 경우 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
)

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

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

추가 리소스

콘텐츠 보기

샘플