Navigation 3 1.2.0부터 ResultEventBus API를 사용하여 대상에서 결과를 반환할 수 있습니다.
ResultEventBus는 두 가지 통신 모델을 제공합니다.
- 이벤트 기반 결과:
ResultEffect를 사용하는 일시적인 일회성 이벤트 (예: 확인 스낵바 표시 또는 부수 효과 트리거) - 상태 기반 결과:
conflateAsState를 사용하여 ComposeState로 최신 결과를 관찰합니다.
결과 이벤트 버스 설정
구성 가능한 대상에서 ResultEventBus를 사용할 수 있도록 하려면 NavDisplay에 전달된 데코레이터 목록에 rememberResultEventBusNavEntryDecorator를 추가합니다. 이렇게 하면 각 대상의 콘텐츠에 LocalResultEventBus 컴포지션 로컬이 제공됩니다.
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
결과 키
ResultEventBus는 키를 사용하여 각 결과를 식별하고 라우팅합니다. 발신자와 수신자는 동일한 키를 사용하여 결과를 일치시킵니다.
결과 키는 다음 두 가지 방법으로 지정할 수 있습니다.
- 명시적 키: 명시적 키 (예:
resultKey = "pickup_address")를 지정할 수 있습니다. 일반적인 유형 (예:String,Boolean또는 기본 요소)을 반환하거나 여러 대상이 동일한 데이터 유형의 다른 인스턴스를 반환하는 경우 명시적 키를 사용합니다. - 유형 파생 키: 명시적 키를 지정하지 않으면
ResultEventBus는 결과 유형의KClass(예:Contact::class.toString())에 대한toString표현을 사용하여 키를 자동으로 생성합니다. 고유한 도메인별 데이터 유형에는 유형 파생 키를 사용합니다.
대상에서 결과 반환
화면 컴포저블을 재사용 가능하고 테스트 가능하게 유지하려면 화면 UI 내에서 직접 LocalResultEventBus에 액세스하지 마세요. 대신 화면에서 콜백 람다를 노출합니다. entryProvider에서 LocalResultEventBus.current를 사용하여 결과를 전송하고 뒤로 이동하여 콜백을 처리합니다.
명시적 결과 키를 사용하여 결과를 전송할 수 있습니다.
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.LocalResultEventBus entry<AddressPickerRoute> { val resultBus = LocalResultEventBus.current AddressPickerScreen( onAddressSelected = { selectedAddress: Address -> resultBus.sendResult( resultKey = "pickup_address", result = selectedAddress ) navigator.goBack() } ) }
유형 파생 키를 사용하여 결과를 전송할 수도 있습니다.
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.LocalResultEventBus entry<ContactPickerRoute> { val resultBus = LocalResultEventBus.current ContactPickerScreen( onContactSelected = { selectedContact: Contact -> resultBus.sendResult(result = selectedContact) navigator.goBack() } ) }
결과 수신
대상은 이벤트 기반 효과 또는 상태 기반 관찰 가능 항목을 사용하여 결과를 사용할 수 있습니다.
| API | 동작 | 권장 사용 사례 |
|---|---|---|
ResultEffect |
대기열에 추가됨: 키에 대해 내보낸 모든 결과를 순서대로 처리합니다. | 일회성 이벤트 및 부작용 (예: 스낵바 표시 또는 ViewModel로 전달) |
conflateAsState |
Conflated: 중간 결과를 삭제하고 최신 결과만 Compose State로 유지합니다. |
가벼운 UI 상태 수정자 (예: 활성 필터 태그 또는 선택 재정의) |
ResultEffect로 일회성 이벤트 처리
분석 트리거, 스낵바 표시, 결과를 ViewModel에 전달과 같은 일회성 이벤트를 처리할 때는 ResultEffect를 사용합니다.
ResultEffect는 수신 결과의 큐를 유지합니다. 특정 키에 대해 여러 결과가 전송되면 ResultEffect는 각 결과를 전송된 순서대로 처리합니다. 또한 ResultEffect는 코루틴 범위에서 실행되므로 효과 본문 내에서 직접 정지 함수를 호출할 수 있습니다.
명시적 결과 키와 연결된 결과를 수신 대기할 수 있습니다.
import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.ResultEffect @Composable fun RideSummaryScreen( onOpenAddressPicker: (key: String) -> Unit, viewModel: RideSummaryViewModel = viewModel() ) { ResultEffect<Address>(resultKey = "pickup_address") { address -> viewModel.onPickupAddressSelected(address) } ResultEffect<Address>(resultKey = "destination_address") { address -> viewModel.onDestinationAddressSelected(address) } RideSummaryContent( pickupAddress = viewModel.pickupAddress, destinationAddress = viewModel.destinationAddress, onPickPickup = { onOpenAddressPicker("pickup_address") }, onPickDestination = { onOpenAddressPicker("destination_address") } ) }
유형 파생 키를 사용하여 결과를 수신 대기할 수도 있습니다.
import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.ResultEffect @Composable fun ComposeMessageScreen( onPickContact: () -> Unit, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, viewModel: ComposeMessageViewModel = viewModel() ) { ResultEffect<Contact> { contact -> // Suspending calls are supported directly in the effect body snackbarHostState.showSnackbar("Selected ${contact.name}") viewModel.onRecipientSelected(contact) } ComposeMessageContent( recipient = viewModel.recipient, onPickContact = onPickContact ) }
대상 간에 이동할 때 ResultEffect는 다음 수명 주기 시퀀스를 통해 실행됩니다.
- 발신자 내보내기: 발신자 대상은
resultBus.sendResult(resultKey = "pickup_address", address)를 사용하여 결과를 전송하고 백 스택을 팝합니다. - 수신자가 컴포지션에 진입: 수신자 대상이 활성 화면이 되고
ResultEffect가 결과를 수신 대기하기 시작합니다. - 수신자가 결과를 처리함:
ResultEffect는 해당 키에 대해 전송된 각 결과에 대해 효과 본문을 수신하고 실행하여 모든 방출을 전송된 순서대로 처리합니다. - 수신자가 컴포지션을 나감: 수신자 대상이 백 스택에서 표시되면
ResultEffect는 컴포지션을 나가고 결과 수신 대기를 중지합니다.
conflateAsState를 사용하여 최신 결과를 상태로 관찰
로컬 UI 상태를 직접 수정하거나 필터링하는 데 최신 결과 값만 필요하고 결과가 업데이트될 때마다 Compose에서 자동으로 재구성하도록 하려면 ResultEventBus에서 conflateAsState를 호출합니다.
명시적 결과 키와 연결된 결과를 관찰할 수 있습니다.
import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.navigation3.runtime.result.LocalResultEventBus @Composable fun ThemePreviewScreen( onOpenColorPicker: (key: String) -> Unit ) { val resultBus = LocalResultEventBus.current val primaryColor by resultBus.conflateAsState<Color>( resultKey = "primary_color", defaultValue = MaterialTheme.colorScheme.primary ) val accentColor by resultBus.conflateAsState<Color>( resultKey = "accent_color", defaultValue = MaterialTheme.colorScheme.tertiary ) ThemePreviewContent( primaryColor = primaryColor, accentColor = accentColor, onPickPrimary = { onOpenColorPicker("primary_color") }, onPickAccent = { onOpenColorPicker("accent_color") } ) }
유형 파생 키를 사용하여 결과를 관찰할 수도 있습니다.
import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.navigation3.runtime.result.LocalResultEventBus @Composable fun FilterableProductListScreen( initialFilter: ProductFilter = ProductFilter.All, onOpenFilterPicker: () -> Unit ) { val resultBus = LocalResultEventBus.current // Observe latest filter result as Compose State, starting with initialFilter val activeFilter by resultBus.conflateAsState<ProductFilter>( defaultValue = initialFilter ) ProductListContent( activeFilter = activeFilter, onOpenFilterPicker = onOpenFilterPicker ) }
호이스트 ResultEventBus
기본적으로 rememberResultEventBusNavEntryDecorator는 rememberResultEventBus를 사용하여 자체 ResultEventBus를 내부적으로 만들고 기억합니다.
다음과 같은 경우 ResultEventBus를 명시적으로 만들고 끌어올릴 수 있습니다.
ResultEventBus인스턴스를 구성 불가능한 구성요소 또는 종속 항목 삽입 그래프에 직접 전달합니다.- 대상 계층 구조 외부의 최상위 앱 스캐폴딩 (예: 앱 바 또는 탐색 창)에서 결과를 전송하거나 관찰합니다.
ResultEventBus를 끌어올리려면 rememberResultEventBus를 사용하여 만들고 rememberResultEventBusNavEntryDecorator(resultEventBus)에 전달합니다.
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.rememberResultEventBus import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator import androidx.navigation3.ui.NavDisplay // Hoist the ResultEventBus at the top level val resultEventBus = rememberResultEventBus() // Pass the hoisted bus to the decorator val resultEventBusNavEntryDecorator = rememberResultEventBusNavEntryDecorator<NavKey>( resultEventBus = resultEventBus ) NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), resultEventBusNavEntryDecorator ) )
결과 관리 및 삭제하기
대상에서 일회성 결과를 사용하면 removeResult를 사용하여 이벤트 버스에서 삭제합니다. 이렇게 하면 대상이 컴포지션에 다시 들어갈 때 버스가 이전 이벤트를 새 옵저버에 다시 전송하지 않습니다.
import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.LocalResultEventBus import androidx.navigation3.runtime.result.ResultEffect @Composable fun NotificationSettingsScreen( viewModel: NotificationViewModel = viewModel() ) { val resultBus = LocalResultEventBus.current ResultEffect<ConfirmationResult>(resultKey = "confirm_permission") { confirmation -> viewModel.onPermissionConfirmed(confirmation) // Clear the result after consumption to prevent re-delivery resultBus.removeResult(resultKey = "confirm_permission") } }
명시적 키 (resultBus.removeResult(resultKey)) 또는 유형 파생 키 (resultBus.removeResult<T>())로 결과를 삭제할 수 있습니다. 키 일치에 관한 자세한 내용은 결과 키를 참고하세요.
레시피
다양한 결과 전달 전략을 보여주는 실행 가능한 전체 코드 예는 다음 레시피를 참고하세요.