將 Jetpack Navigation 遷移至 Navigation Compose

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

本頁面說明如何從以 Fragment 為基礎的 Jetpack Navigation 遷移至 Navigation Compose,這是將以 View 為基礎的 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 (通常位於 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) 做為引數傳遞。應採用單一真實資訊來源 (例如 資料層) 的形式儲存複雜物件。詳情請參閱「瀏覽時擷取複雜資料」。

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

限制

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

逐步遷移至 Navigation Compose

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

轉場動畫

Navigation 2.7.0-alpha01 開始,AnimatedNavHost 先前支援的設定自訂轉場效果功能,現在也直接支援 NavHost。詳情請參閱版本資訊

瞭解詳情

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

  • Navigation Compose 程式碼研究室:透過實作程式碼研究室,瞭解 Navigation Compose 的基本概念。
  • Now in Android 存放區:這是一款功能完整的 Android 應用程式,完全採用 Kotlin 和 Jetpack Compose 建構,遵循 Android 設計和開發最佳做法,並包含 Navigation Compose。
  • 將 Sunflower 遷移至 Jetpack Compose:這篇網誌文章記錄了 Sunflower 範例應用程式從 View 遷移至 Compose 的過程,其中也包含遷移至 Navigation Compose 的步驟。
  • 適用於各種螢幕的 Jetnews:這篇部落格文章記錄了 Jetpack Compose 和 Navigation Compose 如何支援所有螢幕,並記錄了 Jetnews 範例的重構和遷移作業。