Jetpack Navigation を Navigation Compose に移行する

Navigation Compose API を使用すると、Jetpack Navigation のコンポーネント、インフラストラクチャ、機能を活用しながら、Compose アプリ内のコンポーザブル間を移動できます。

このページでは、ビューベースの UI から Jetpack Compose への大規模な移行の一環として、フラグメント ベースの Jetpack Navigation から Navigation Compose に移行する方法について説明します。

移行の前提条件

すべてのフラグメントを対応する画面コンポーザブルに置き換えることができるようになったら、Navigation Compose に移行できます。画面コンポーザブルには Compose と View のコンテンツを混在させることができますが、Navigation Compose の移行を有効にするには、すべてのナビゲーション デスティネーションがコンポーザブルである必要があります。それまでは、相互運用 View と Compose のコードベースでフラグメント ベースの Navigation コンポーネントを引き続き使用する必要があります。詳細については、ナビゲーションの相互運用に関するドキュメントをご覧ください。

移行手順

推奨される移行戦略に従うか、別のアプローチを取るかにかかわらず、すべてのナビゲーション デスティネーションが画面コンポーザブルになり、フラグメントがコンポーザブル コンテナとしてのみ機能するようになります。この段階で、Navigation Compose に移行できます。

アプリがすでに UDF 設計パターンとアーキテクチャ ガイドに沿っている場合、Jetpack Compose と Navigation Compose への移行では、UI レイヤ以外のアプリの他のレイヤを大幅にリファクタリングする必要はありません。

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 class または class を使用します。

    @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"))
                    }
                )
            }
            <compos>ableSecond { backSt>ackEntry -
                val secondRoute = backStackEnt<ry.toR>outeSecond()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // .nippets.kt

  9. すべてのフラグメント、関連する XML レイアウト、不要なナビゲーションなどのリソース、古いフラグメントと Jetpack Navigation の依存関係を削除します。

同じ手順と Navigation Compose 関連の詳細については、セットアップ ドキュメントをご覧ください。

一般的なユースケース

どの Navigation コンポーネントを使用する場合でも、同じナビゲーションの原則が適用されます。

移行時の一般的なユースケースは次のとおりです。

これらのユースケースの詳細については、Compose を使用したナビゲーションをご覧ください。

移動時の複雑なデータの取得

移動時には複雑なデータ オブジェクトを渡さないことを強くおすすめします。代わりに、ナビゲーション アクションの実行時に引数として必要最低限の情報(固有識別子やその他の形式の ID など)を渡します。複雑なオブジェクトは信頼できる単一の情報源(データレイヤなど)にデータとして保存する必要があります。詳細については、移動時の複雑なデータの取得をご覧ください。

Fragment が複雑なオブジェクトを引数として渡している場合は、まず、データレイヤからこれらのオブジェクトを保存、取得できるようにコードをリファクタリングすることを検討してください。例については、Now in Android リポジトリをご覧ください。

制限事項

このセクションでは、Navigation Compose の現在の制限事項について説明します。

Navigation Compose への段階的な移行

現時点では、コード内でフラグメントをデスティネーションとして使用している間は、Navigation Compose を使用できません。Navigation Compose の使用を開始するには、すべてのデスティネーションをコンポーザブルにする必要があります。この機能リクエストは Issue Tracker で追跡できます。

遷移アニメーション

Navigation 2.7.0-alpha01 以降、カスタム遷移の設定(以前は AnimatedNavHost から)が NavHost で直接サポートされるようになりました。詳細については、リリースノートをご覧ください。

詳細

Navigation Compose への移行の詳細については、次のリソースをご覧ください。

  • Navigation Compose Codelab: 実践的な Codelab で Navigation Compose の基本を学びます。
  • Now in Android リポジトリ: Kotlin と Jetpack Compose のみで構築された、完全に機能する Android アプリ。Android の設計と開発のベスト プラクティスに沿っており、Navigation Compose が含まれています。
  • Sunflower を Jetpack Compose に移行する: Sunflower サンプルアプリを View から Compose に移行するプロセスを記録したブログ投稿。Navigation Compose への移行についても説明しています。
  • あらゆる画面に対応した Jetnews: Jetpack Compose と Navigation Compose を使用してすべての画面をサポートするための Jetnews サンプルのリファクタリングと移行について説明したブログ投稿。