탐색 코드 캡슐화

Kotlin DSL을 사용하여 그래프를 구성할 때 대상과 탐색 이벤트를 단일 파일에 유지하기는 어려울 수 있습니다. 독립적인 특성이 여러 개 있는 경우 특히 그렇습니다.

대상 추출

대상을 NavGraphBuilder 확장 함수로 이동해야 합니다. 광고 소재는 경로를 정의하는 경로 및 표시되는 화면 가까이에 있어야 합니다. 예를 들어 연락처 목록을 표시하는 대상을 만드는 다음 앱 수준 코드를 생각해 보세요.

// MyApp.kt

@Serializable
object Contacts

@Composable
fun MyApp() {
  ...
  NavHost(navController, startDestination = Contacts) {
    composable<Contacts> { ContactsScreen( /* ... */ ) }
  }
}

탐색 관련 코드는 별도의 파일로 이동해야 합니다.

// ContactsNavigation.kt

@Serializable
object Contacts

fun NavGraphBuilder.contactsDestination() {
    composable<Contacts> { ContactsScreen( /* ... */ ) }
}

// MyApp.kt

@Composable
fun MyApp() {
  ...
  NavHost(navController, startDestination = Contacts) {
     contactsDestination()
  }
}

이제 경로 및 대상 정의가 기본 앱과 별개이므로 독립적으로 업데이트할 수 있습니다. 기본 앱은 단일 확장 함수에만 종속됩니다. 이 경우는 NavGraphBuilder.contactsDestination()입니다.

NavGraphBuilder 확장 함수는 스테이트리스(Stateless) 화면 수준의 구성 가능한 함수와 탐색 관련 로직 간의 가교를 형성합니다. 이 레이어는 상태의 출처와 이벤트를 처리하는 방법도 정의할 수 있습니다.

다음 스니펫은 연락처 세부정보를 표시할 새로운 대상을 도입하고, 연락처 세부정보를 표시하도록 탐색 이벤트를 노출하도록 기존 연락처 목록 대상을 업데이트합니다.

다음은 다른 모듈에서 액세스할 수 없도록 자체 모듈에 internal가 될 수 있는 일반적인 화면 세트입니다.

// ContactScreens.kt

// Displays a list of contacts
@Composable
internal fun ContactsScreen(
  uiState: ContactsUiState,
  onNavigateToContactDetails: (contactId: String) -> Unit
) { ... }

// Displays the details for an individual contact
@Composable
internal fun ContactDetailsScreen(contact: ContactDetails) { ... }

대상 만들기

다음 NavGraphBuilder 확장 함수는 ConversationScreen 컴포저블을 표시하는 대상을 만듭니다. 또한 이제 화면 UI 상태를 제공하고 화면 관련 비즈니스 로직을 처리하는 ViewModel에 화면을 연결합니다.

탐색 이벤트(예: 연락처 세부정보 대상으로 이동)는 ViewModel에 의해 처리되지 않고 호출자에게 노출됩니다.

// ContactsNavigation.kt

@Serializable
object Contacts

// Adds contacts destination to `this` NavGraphBuilder
fun NavGraphBuilder.contactsDestination(
  // Navigation events are exposed to the caller to be handled at a higher level
  onNavigateToContactDetails: (contactId: String) -> Unit
) {
  composable<Contacts> {
    // The ViewModel as a screen level state holder produces the screen
    // UI state and handles business logic for the ConversationScreen
    val viewModel: ContactsViewModel = hiltViewModel()
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    ContactsScreen(
      uiState,
      onNavigateToContactDetails
    )
  }
}

동일한 접근 방식을 사용하여 ContactDetailsScreen를 표시하는 대상을 만들 수 있습니다. 이 경우 뷰 모델에서 UI 상태를 가져오는 대신 NavBackStackEntry에서 직접 가져올 수 있습니다.

// ContactsNavigation.kt

@Serializable
internal data class ContactDetails(val id: String)

fun NavGraphBuilder.contactDetailsScreen() {
  composable<ContactDetails> { navBackStackEntry ->
    ContactDetailsScreen(contact = navBackStackEntry.toRoute())
  }
}

탐색 이벤트 캡슐화

대상을 캡슐화하는 것과 같은 방식으로 탐색 이벤트를 캡슐화하여 경로 유형이 불필요하게 노출되지 않도록 할 수 있습니다. 이렇게 하려면 NavController에서 확장 함수를 만들면 됩니다.

// ContactsNavigation.kt

fun NavController.navigateToContactDetails(id: String) {
  navigate(route = ContactDetails(id = id))
}

총정리

이제 연락처를 표시하는 탐색 코드가 앱의 탐색 그래프에서 깔끔하게 분리됩니다. 앱이 다음을 충족해야 합니다.

  • NavGraphBuilder 확장 함수를 호출하여 대상 만들기
  • 탐색 이벤트에 대해 NavController 확장 함수를 호출하여 이러한 대상을 연결합니다.
// MyApp.kt

@Composable
fun MyApp() {
  ...
  NavHost(navController, startDestination = Contacts) {
     contactsDestination(onNavigateToContactDetails = { contactId ->
        navController.navigateToContactDetails(id = contactId)
     })
     contactDetailsDestination()
  }
}

요약

  • 탐색 코드를 별도의 파일에 배치하여 관련 화면 세트의 탐색 코드를 캡슐화합니다.
  • NavGraphBuilder에서 확장 함수를 만들어 대상 노출
  • NavController에서 확장 함수를 만들어 탐색 이벤트 노출
  • internal를 사용하여 화면 및 경로 유형을 비공개로 유지