將 Jetpack Navigation 遷移至 Navigation Compose

Navigation Compose API 可讓您在 Compose 應用程式中瀏覽不同可組合項,同時運用 Jetpack Navigation 的元件、基礎架構和功能。

本頁面說明如何從以片段為基礎的 Jetpack Navigation 遷移至 Navigation Compose,這是以檢視區塊為基礎的 UI 遷移至 Jetpack Compose 的一部分。

遷移作業必備條件

您可以將所有 Fragment 替換為對應的畫面可組合項後,再遷移至 Navigation Compose。畫面可組合項可以包含Compose 和 View 內容的組合,但所有導覽目的地都必須是可組合項,才能啟用 Navigation Compose 遷移作業。在此之前,您應繼續在互通的 View 和 Compose 程式碼庫中使用以片段為基礎的 Navigation 元件。詳情請參閱導覽互通性說明文件

您不一定要在僅限使用 Compose 的應用程式中使用 Navigation Compose。只要保留片段來代管可組合函式內容,您就可以繼續使用 以片段為基礎的 Navigation 元件

遷移步驟

無論您是採用建議的遷移策略,還是其他方法,最終都會讓所有導覽目的地成為畫面可組合項,而片段只會做為可組合項容器。此時,您可以遷移至 Navigation Compose。

如果應用程式已採用 UDF 設計模式架構指南,除了 UI 層之外,遷移至 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 的可組合項都能存取的位置設定 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,這個步驟只會將這些畫面可組合項從片段擷取至 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 給每個畫面可組合函式的方式。您通常可以透過 hiltViewModel 使用 Hilt 注入功能,以及與 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 或其他形式的 ID) 做為引數傳遞。您應採用單一真實資訊來源 (例如資料層) 的形式儲存複雜物件。詳情請參閱「瀏覽時擷取複雜資料」。

如果片段將複雜物件做為引數傳遞,請先重構程式碼,以便從資料層儲存及擷取這些物件。如需範例,請參閱 Now in Android 存放區

限制

本節說明 Navigation Compose 目前的限制。

逐步遷移至 Navigation Compose

目前,您無法在程式碼中仍使用片段做為目的地時,使用 Navigation Compose。如要開始使用 Navigation Compose,所有目的地都必須是可組合函式。您可以在 Issue Tracker 上追蹤這項功能要求。

轉場動畫

Navigation 2.7.0-alpha01 開始,系統現在直接在 NavHost 中支援設定自訂轉場效果,先前則是在 AnimatedNavHost 中支援。詳情請參閱版本資訊

瞭解詳情

如要進一步瞭解如何遷移至 Navigation Compose,請參閱下列資源: