将 Jetpack Navigation 迁移到 Navigation Compose

借助 Navigation Compose API,您可以在 在 Compose 应用中利用 Jetpack Navigation 组件, 基础架构和功能。

本页介绍了如何从基于 fragment 的 Jetpack Navigation 迁移到 Navigation Compose,是基于 View 的界面向 Jetpack 大规模迁移的一部分 写邮件。

迁移先决条件

当您能够替换掉所有之前使用的 具有相应屏幕可组合项的 fragment。屏幕可组合项可以包含 混合使用 Compose 和 View 内容,但所有导航目的地都必须 可组合项来启用 Navigation Compose 迁移。在此之前,您应该 继续在互操作性 View 中使用基于 Fragment 的 Navigation 组件 Compose 代码库。如需了解详情,请参阅导航互操作性文档 信息。

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

迁移步骤

无论您是遵循我们推荐的迁移策略,还是采取 或者使用另一种方法,此时所有导航目的地 screen 可组合项,其中 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. 为每个导航目的地创建类型。将 data object 用于 不需要任何数据且data classclass 需要数据的目的地。

    @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 界面架构中的指南, 具体而言,应如何将 ViewModel 和导航事件传递给 可组合项,下一步是更改向可组合项提供 ViewModel 的方式 每个 screen 可组合项。您通常可以使用 Hilt 注入及其集成 通过 hiltViewModel 使用 Compose 和 Navigation:

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

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

    可通过创建路线实例将数据传递到目的地 类。然后,您可以直接获取 从目的地的返回堆栈条目或从 ViewModel 使用 SavedStateHandle.toRoute()

    @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 依赖项。

您可以在 设置文档

常见用例

无论您使用哪个导航组件,导航组件的 导航应用

迁移的常见用例包括:

有关这些用例的更多详细信息,请参见使用 写邮件

在导航时检索复杂数据

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

如果您的 fragment 将复杂的对象作为参数传递,请考虑重构 以便从代码中存储和提取这些对象 数据层如需了解更多详情,请参阅 Now in Android 代码库 示例。

限制

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

逐步迁移到 Navigation Compose

目前,您无法在使用 Fragment 的同时,使用 Navigation Compose 目标。如需开始使用 Navigation Compose,您的所有 目的地必须是可组合项。您可以在 问题跟踪器

过渡动画

Navigation 2.7.0-alpha01 开始,支持设置自定义 之前从“AnimatedNavHost”改为现在 NavHost 直接支持。请仔细阅读以下内容的版本说明: 。

了解详情

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