Jetpack Navigation を Navigation Compose に移行する

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

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

移行の前提条件

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

Compose 専用アプリで Navigation 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"))
                    }
                )
            }
            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 など)を渡します。複雑なオブジェクトは、データレイヤなどの信頼できる単一の情報源にデータとして保存する必要があります。詳細については、ナビゲーション時の複雑なデータの取得をご覧ください。

フラグメントが複雑なオブジェクトを引数として渡している場合は、まず、これらのオブジェクトをデータレイヤから保存して取得できるようにコードをリファクタリングすることを検討してください。例については、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: Jetnews サンプルのリファクタリングと移行を文書化したブログ投稿。Jetpack Compose と Navigation Compose を使用して、あらゆる画面をサポートしています。