封装您的导航代码

当使用 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 扩展函数构成了无状态屏幕级可组合函数与导航特定逻辑之间的桥梁。此层还可以定义状态的来源以及处理事件的方式。

示例

以下代码段引入了用于显示联系人详细信息的新目的地,并更新了现有的联系人列表目的地以 公开导航事件 以显示联系人详细信息。

这是一个可以 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 扩展函数创建了一个显示 ContactsScreen 可组合的目的地。此外,它现在将屏幕与一个 ViewModel 连接起来,该 ViewModel 提供屏幕 UI 状态并处理与屏幕相关的业务逻辑。

导航事件(如导航到联系人详细信息目的地)将公开给调用方,而不是由 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 的目的地。在这种情况下,您可以直接从 NavBackStackEntry 获取 UI 状态,而不是从视图模型获取。

// 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))
}

整合

用于显示联系人的导航代码现在已与应用程序的导航图 cleanly separated。应用程序需要

  • 调用 NavGraphBuilder 扩展函数来创建目标
  • 通过调用 NavController 扩展函数来连接这些目标以进行导航事件
// MyApp.kt

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

总结

  • 通过将相关屏幕集的导航代码放在单独的文件中来封装你的导航代码
  • 通过在 NavGraphBuilder 上创建扩展函数来公开目标
  • 通过在 NavController 上创建扩展函数来公开导航事件
  • 使用 internal 来保持屏幕和路由类型私有