将 Jetpack Navigation 迁移到 Navigation Compose

借助 Navigation Compose API,您可以在 Compose 应用中的可组合项之间导航,同时利用 Jetpack Navigation 的组件、基础架构和功能。

本页将介绍如何从基于 fragment 的 Jetpack Navigation 迁移到 Navigation Compose,这是基于 View 的界面迁移到 Jetpack Compose 的整个流程。

迁移先决条件

当您能够将所有 fragment 替换为相应的屏幕可组合项后,即可迁移到 Navigation Compose。屏幕可组合项可以混合包含 Compose 和 View 内容,但所有导航目的地都必须是可组合项,这样才能实现 Navigation Compose 迁移。在此之前,您应继续在互操作性 View 和 Compose 代码库中使用基于 fragment 的导航组件。如需了解详情,请参阅导航互操作性文档

在纯 Compose 应用中使用 Navigation Compose 不是前提条件。只要您保留用于托管可组合项内容的 fragment,就可以继续使用 基于 fragment 的导航组件

迁移步骤

无论您是遵循我们建议的迁移策略还是采用其他方法,最终都会到达所有导航目的地都是屏幕可组合项,而 fragment 仅充当可组合容器的程度。在此阶段,您可以迁移到 Navigation Compose。

如果您的应用已遵循 UDF 设计模式和我们的架构指南,那么迁移到 Jetpack Compose 和 Navigation Compose 应该不需要对应用的其他层进行重大重构,界面层除外。

如需迁移到 Navigation Compose,请按以下步骤操作:

  1. Navigation Compose 依赖项添加到您的应用。
  2. 创建 App-level 可组合项,并将其添加到 Activity 作为 Compose 入口点,替换 View 布局的设置:

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. 请在一个位置设置 NavController,以便需要引用它的所有可组合项都可以访问它(这通常位于 App 可组合项内)。此方法遵循状态提升的原则,可让您将 NavController 用作在可组合屏幕之间导航和维护返回堆栈的可信来源:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  4. 在 App 可组合项内创建应用的 NavHost 并传递 navController

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = "first") {
            // ...
        }
    }

  5. 添加 composable 目的地以构建导航图。如果每个屏幕之前都已迁移到 Compose,则此步骤只包括从 fragment 将这些屏幕可组合项提取到 composable 目的地:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = "first") {
            composable("first") {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable("second") {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  6. 如果您遵循了设计 Compose 界面(特别是应如何将 ViewModel 和导航事件传递给可组合项)中的指南,那么下一步就是更改向每个屏幕可组合项提供 ViewModel 的方式。您通常可以通过 hiltViewModel 将 Hilt 注入及其集成点与 Compose 和 Navigation 搭配使用:

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  7. 将所有 findNavController() 导航调用替换为 navController 调用,并将这些调用作为导航事件传递给每个可组合屏幕,而不是传递整个 navController。此方法遵循了将可组合函数中的事件公开给调用方的最佳实践,并保留 navController 作为单一可信来源。

    1. 如果您之前使用过 Safe Args 插件来生成导航路线和操作,请将其替换为 route,这是指向可组合项的字符串路径,对于每个目的地,此路径都是唯一的。
    2. 如需了解如何在传递数据时替换 Safe Args,请参阅使用参数进行导航
    3. 如需了解 Navigation Compose 中的类型安全,请参阅下文的 Safe Args 部分

      @Composable
      fun SampleNavHost(
          navController: NavHostController
      ) {
          NavHost(navController = navController, startDestination = "first") {
              composable("first") {
                  FirstScreen(
                      onButtonClick = {
                          // findNavController().navigate(firstScreenToSecondScreenAction)
                          navController.navigate("second_screen_route")
                      }
                  )
              }
              composable("second") {
                  SecondScreen(
                      onIconClick = {
                          // findNavController().navigate(secondScreenToThirdScreenAction)
                          navController.navigate("third_screen_route")
                      }
                  )
              }
              // ...
          }
      }

  8. 移除所有 fragment、相关的 XML 布局、不必要的导航和其他资源,以及过时的 fragment 和 Jetpack Navigation 依赖项。

您可以在设置文档中找到相同的步骤以及更多与 Navigation Compose 相关的详细信息。

常见用例

无论您使用哪个 Navigation 组件,相同的导航原则都适用

迁移的常见用例包括:

如需详细了解这些用例,请参阅使用 Compose 进行导航

Safe Args

与 Jetpack Navigation 不同,Navigation Compose 不支持使用 Safe Args 插件生成代码。您可以改为使用 Navigation Compose 实现类型安全,只需构建代码以使其在运行时具有类型安全即可。

在导航时检索复杂数据

Navigation Compose 基于字符串路由,与 Jetpack Navigation 不同,不支持将自定义 Parcelable 和 Serializable 作为参数传递

我们强烈建议您在导航时不要传递复杂的数据对象。在执行导航操作时,应改为将最少量的必要信息(例如唯一标识符或其他形式的 ID)作为参数进行传递。您应将复杂对象作为数据存储在单一可信来源(例如数据层)中。如需了解详情,请参阅在导航时检索复杂数据

如果您的 fragment 将复杂的对象作为参数传递,不妨考虑先重构代码,以便支持从数据层存储和提取这些对象。如需查看示例,请参阅 Now in Android 代码库

限制

本部分介绍 Navigation Compose 的当前限制。

逐步迁移到 Navigation Compose

目前,您无法在使用 Navigation Compose 的同时在代码中使用 fragment 作为目的地。如需开始使用 Navigation Compose,您的所有目的地都必须是可组合项。您可以在问题跟踪器上跟踪此功能请求

过渡动画

Navigation 2.7.0-alpha01 开始,NavHost 现在直接支持设置自定义转换(以前来自 AnimatedNavHost)。如需了解详情,请仔细阅读版本说明

了解详情

如需详细了解如何迁移到 Navigation Compose,请参阅以下资源:

  • Navigation Compose Codelab:通过实操 Codelab 了解 Navigation Compose 的基础知识。
  • Now in Android 代码库:完全使用 Kotlin 和 Jetpack Compose 构建的功能齐全的 Android 应用,该应用遵循 Android 设计和开发最佳实践,并且包含 Navigation Compose。
  • 将 Sunflower 迁移到 Jetpack Compose:这篇博文记录了 Sunflower 示例应用从 View 迁移到 Compose 的过程,其中包括迁移到 Navigation Compose 的过程。
  • 适用于每个屏幕的 Jetnews:一篇博客文章,记录了 Jetnews 示例的重构和迁移,以支持使用 Jetpack Compose 和 Navigation Compose 的所有屏幕。