将 Jetpack Navigation 迁移到 Navigation Compose

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

此页面介绍如何将基于 Fragment 的 Jetpack Navigation 迁移到 Navigation Compose,这是将基于 View 的 UI 迁移到 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 不应该需要对应用的其他层进行重大重构,除了 UI 层。

要迁移到 Navigation Compose,请按照以下步骤操作

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

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

  3. 为每个导航目标创建类型。对于不需要任何数据的目标,使用 数据对象;对于需要数据的目标,使用 数据类

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. 在所有需要引用它的可组合项都可以访问它的地方设置 NavController(这通常在您的 App 可组合项内)。这种方法遵循 状态提升原则,并允许您使用 NavController 作为在可组合屏幕之间导航和维护返回堆栈的唯一事实来源

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

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

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

  6. 添加 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(/* ... */)
            }
            // ...
        }
    }

  7. 如果您遵循了关于 架构您的 Compose UI 的指导,特别是关于如何将 ViewModel 和导航事件传递给可组合项,则下一步是更改您向每个屏幕可组合项提供 ViewModel 的方式。您通常可以使用 Hilt 注入及其与 Compose 和 Navigation 通过 hiltViewModel 的集成点。

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

  8. 将所有 findNavController() 导航调用替换为 navController 调用,并将这些作为导航事件传递给每个可组合屏幕,而不是传递整个 navController。这种方法遵循 最佳实践,即向调用者公开可组合函数的事件,并将 navController 保持为唯一事实来源。

    可以通过为该目标定义的路由类创建实例来将数据传递到目标。然后可以直接从目标处的返回堆栈条目或使用 SavedStateHandle.toRoute()ViewModel 获取它。

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. 删除所有 Fragment、相关的 XML 布局、不必要的导航和其他资源,以及陈旧的 Fragment 和 Jetpack Navigation 依赖项。

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

常见用例

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

迁移时的常见用例包括:

有关这些用例的更多详细信息,请参阅 使用 Compose 进行导航

导航时检索复杂数据

我们强烈建议不要在导航时传递复杂的 数据对象。相反,在执行导航操作时,请将最小必要的信息(例如唯一标识符或其他形式的 ID)作为参数传递。您应该将复杂对象作为数据存储在唯一的事实来源中,例如 数据层。有关详细信息,请参阅 导航时检索复杂数据

如果您的 Fragment 将复杂对象作为参数传递,请考虑首先重构您的代码,以便能够从数据层存储和获取这些对象。有关示例,请参阅 Now in Android 代码库

限制

本节介绍 Navigation Compose 的当前限制。

增量迁移到 Navigation Compose

目前,您不能在代码中仍然使用 Fragment 作为目标时使用 Navigation Compose。要开始使用 Navigation Compose,所有目标都需要是可组合项。您可以跟踪 问题跟踪器上的此功能请求

过渡动画

Navigation 2.7.0-alpha01 开始,对设置自定义过渡的支持(以前来自 AnimatedNavHost)现在在 NavHost 中直接受支持。阅读 发行说明 以了解更多信息。

了解更多

有关迁移到 Navigation Compose 的更多信息,请参阅以下资源:

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