将 Jetpack Navigation 迁移到 Navigation Compose

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

本页介绍了如何从基于 Fragment 的 Jetpack Navigation 迁移到 Navigation Compose,这是从基于 View 的界面迁移到 Jetpack Compose 的更大迁移的一部分。

迁移先决条件

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

迁移步骤

无论您是遵循我们推荐的迁移策略还是采用 其他方法,您都会达到一个所有导航目的地都是 界面可组合项,而 fragment 仅充当可组合容器的阶段。在此阶段,您可以迁移到 Navigation Compose。

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

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

  1. Navigation Compose 依赖项 添加到您的应用。
  2. 创建一个 App-level 可组合项,并将其作为 Compose 入口点添加到 Activity,以替换 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。此方法遵循状态提升的原则 ,让您可以使用 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 和 Navigation 事件传递给可组合函数,那么下一步就是更改您向每个界面可组合函数提供 ViewModel 的方式。您通常可以使用 Hilt 注入及其集成 点(通过 hiltViewModel)与 Compose 和 Navigation:

    @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 相关的详细信息。

常见应用场景

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

迁移时的常见应用场景包括:

如需详细了解这些应用场景,请参阅使用 Compose进行导航。

在导航时检索复杂数据

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

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

限制

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

增量迁移到 Navigation Compose

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

过渡动画

Navigation 2.7.0-alpha01 开始,对设置自定义 过渡(之前来自 AnimatedNavHost)的支持现在直接在 NavHost 中提供。如需了解详情,请阅读版本说明

了解详情

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

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