Compose でのナビゲーション

Jetpack をサポートする Navigation コンポーネント アプリを作成する。コンポーザブル間を移動するには Navigation コンポーネントのインフラストラクチャと 説明します。

設定

Compose をサポートするには、アプリ モジュールの build.gradle ファイル:

Groovy

dependencies {
    def nav_version = "2.8.3"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.8.3"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

始める

アプリにナビゲーションを実装する場合は、ナビゲーション ホストを実装します。 グラフ、コントローラなどがあります詳細については、ナビゲーションの概要をご覧ください。

Compose で NavController を作成する方法については、Compose の セクション(ナビゲーション コントローラを作成する)をご覧ください。

NavHost を作成する

Compose で NavHost を作成する方法については、Compose のセクションをご覧ください。 ナビゲーション グラフを設計するをご覧ください。

コンポーザブルへの移動について詳しくは、 アーキテクチャ内の宛先 ご覧ください

Navigation Compose では、コンポーザブルのデスティネーション間で引数を渡すこともできます。これを行うには、基本のナビゲーション ライブラリを使用する場合にディープリンクに引数を追加する方法と同様に、引数のプレースホルダをルートに追加する必要があります。

NavHost(startDestination = "profile/{userId}") {
    ...
    composable("profile/{userId}") {...}
}

デフォルトでは、すべての引数が文字列として解析されます。arguments パラメータの composable()NamedNavArgument オブジェクトのリストを受け入れます。Google Chat では navArgument() メソッドを使用して NamedNavArgument をすばやく作成し、 次に、その正確な type を指定します。

NavHost(startDestination = "profile/{userId}") {
    ...
    composable(
        "profile/{userId}",
        arguments = listOf(navArgument("userId") { type = NavType.StringType })
    ) {...}
}

composable() 関数のラムダで使用可能な NavBackStackEntry から引数を抽出する必要があります。

composable("profile/{userId}") { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

デスティネーションに引数を渡すには、navigate を呼び出すときにルートに引数を追加する必要があります。

navController.navigate("profile/user1234")

サポートされる型の一覧については、デスティネーション間でデータを渡すをご覧ください。

操作時に複雑なデータを取得

移動時には複雑なデータ オブジェクトを渡すのではなく、ナビゲーション アクションの実行時に引数として必要最低限の情報(一意の識別子やその他の形式の ID など)を渡すことを強くおすすめします。

// Pass only the user ID when navigating to a new destination as argument
navController.navigate("profile/user1234")

複雑なオブジェクトは、 作成します移動後にデスティネーションに到達したら、渡された ID を使用して、信頼できる単一の情報源から必要な情報を読み込むことができます。次の処理を行う ViewModel の引数を取得するには、 データレイヤーにアクセスする場合は、ViewModelSavedStateHandle を使用します。

class UserViewModel(
    savedStateHandle: SavedStateHandle,
    private val userInfoRepository: UserInfoRepository
) : ViewModel() {

    private val userId: String = checkNotNull(savedStateHandle["userId"])

    // Fetch the relevant user information from the data layer,
    // ie. userInfoRepository, based on the passed userId argument
    private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(userId)

// …

}

この手法により、設定の変更時のデータ損失や、対象のオブジェクトの更新時や変更時の不整合を防ぐことができます。

複雑なデータを引数として渡すのを避けるべき理由についての詳しい説明と、サポートされる引数タイプのリストについては、デスティネーション間でデータを渡すをご覧ください。

オプションの引数を追加する

Navigation Compose は、省略可能なナビゲーション引数もサポートしています。省略可能な引数は、次の 2 つの点で必須の引数とは異なります。

  • クエリ パラメータの構文("?argName={argName}")を使用して指定する必要があります。
  • defaultValue を設定するか、nullable = true(デフォルト値を暗黙的に null に設定)を指定する必要があります。

つまり、省略可能なすべての引数をリストとして composable() 関数に明示的に追加する必要があります。

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

デスティネーションに渡される引数がない場合でも、代わりに defaultValue の「user1234」が使用されます。

ルートを通じて引数を処理するという構造のため、コンポーザブルは Navigation から完全に独立しており、テストがかなり容易になります。

Navigation Compose は、composable() 関数の一部として定義できる暗黙的なディープリンクもサポートしています。その deepLinks パラメータは、 NavDeepLink オブジェクトは、 navDeepLink() メソッド:

val uri = "https://www.example.com"

composable(
    "profile?id={id}",
    deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("id"))
}

これらのディープリンクを使用すると、特定の URL、アクション、または MIME タイプを 作成します。デフォルトでは、これらのディープリンクは外部アプリには公開されません。宛先 これらのディープリンクを外部に公開するには、 <intent-filter> 要素をアプリの manifest.xml ファイルに追加します。ディープ ラーニングを 場合、 マニフェストの <activity> 要素を使用します。

<activity …>
  <intent-filter>
    ...
    <data android:scheme="https" android:host="www.example.com" />
  </intent-filter>
</activity>

別のアプリによってディープリンクがトリガーされると、Navigation はそのコンポーザブルに自動的にディープリンクします。

この同じディープリンクを使用して、コンポーザブルの適切なディープリンクを含む PendingIntent を作成することもできます。

val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
    Intent.ACTION_VIEW,
    "https://www.example.com/$id".toUri(),
    context,
    MyActivity::class.java
)

val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
    addNextIntentWithParentStack(deepLinkIntent)
    getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}

この deepLinkPendingIntent を他の PendingIntent と同様に使用して、ディープリンクのデスティネーションでアプリを開くことができます。

ネスト ナビゲーション

ネストされたナビゲーション グラフの作成方法については、以下をご覧ください。 ネストされたグラフ

下部のナビゲーション バーとの統合

コンポーザブルの階層内の上位のレベルで NavController を定義することで、Navigation を他のコンポーネント(ボトム ナビゲーション コンポーネントなど)と接続できます。これにより、下部のアイコンを選択して移動できるようになります。 表示されます。

BottomNavigation コンポーネントと BottomNavigationItem コンポーネントを使用するには、androidx.compose.material の依存関係を Android アプリに追加します。

Groovy

dependencies {
    implementation "androidx.compose.material:material:1.7.5"
}

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

dependencies {
    implementation("androidx.compose.material:material:1.7.5")
}

android {
    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

下部のナビゲーション バーのアイテムをナビゲーション グラフ内のルートにリンクするには、デスティネーションのルートと文字列のリソース ID を含むシールクラス(下記の Screen など)を定義することをおすすめします。

sealed class Screen(val route: String, @StringRes val resourceId: Int) {
    object Profile : Screen("profile", R.string.profile)
    object FriendsList : Screen("friendslist", R.string.friends_list)
}

次に、そのアイテムを BottomNavigationItem が使用可能なリスト内に配置します。

val items = listOf(
   Screen.Profile,
   Screen.FriendsList,
)

BottomNavigation コンポーザブルで、currentBackStackEntryAsState() 関数を使用して現在の NavBackStackEntry を取得します。このエントリにより、現在の NavDestination にアクセスできるようになります。各 Pod の選択された状態は、 アイテムのルートを比較して、BottomNavigationItem を決定できます。 現在のデスティネーションとその親デスティネーションのルートを、 ネストされたナビゲーションを使用している場合に、 NavDestination 階層。

アイテムをタップするとそのアイテムに移動するように、onClick ラムダを navigate の呼び出しに接続するためにもアイテムのルートが使用されます。saveStaterestoreState のフラグを使用すると、ボトム ナビゲーション アイテムを切り替えるときに、そのアイテムの状態とバックスタックが正しく保存され、復元されます。

val navController = rememberNavController()
Scaffold(
  bottomBar = {
    BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentDestination = navBackStackEntry?.destination
      items.forEach { screen ->
        BottomNavigationItem(
          icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
          label = { Text(stringResource(screen.resourceId)) },
          selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
          onClick = {
            navController.navigate(screen.route) {
              // Pop up to the start destination of the graph to
              // avoid building up a large stack of destinations
              // on the back stack as users select items
              popUpTo(navController.graph.findStartDestination().id) {
                saveState = true
              }
              // Avoid multiple copies of the same destination when
              // reselecting the same item
              launchSingleTop = true
              // Restore state when reselecting a previously selected item
              restoreState = true
            }
          }
        )
      }
    }
  }
) { innerPadding ->
  NavHost(navController, startDestination = Screen.Profile.route, Modifier.padding(innerPadding)) {
    composable(Screen.Profile.route) { Profile(navController) }
    composable(Screen.FriendsList.route) { FriendsList(navController) }
  }
}

ここでは、NavController.currentBackStackEntryAsState() メソッドを使用して NavHost 関数から navController の状態をホイスティングし、BottomNavigation コンポーネントと共有しています。つまり、BottomNavigation は自動的に最新の状態を保持します。

Navigation Compose での型安全性

このページのコードはタイプセーフではありません。navigate() を呼び出すこともできます。 存在しないルートや誤った引数を指定したためです。ただし、 実行時にタイプセーフなナビゲーション コードを構造化します。これにより次のことが確保され、クラッシュが回避されます。

  • デスティネーションまたはナビゲーション グラフに移動するときに指定する引数は適切な型であり、必要な引数がすべて存在している。
  • SavedStateHandle から取得する引数は正しい型である。

詳細については、Kotlin DSL と Navigation での型安全性をご覧ください。 Compose にあります。

相互運用性

Compose で Navigation コンポーネントを使用するには、次の 2 つの方法があります。

  • フラグメントに Navigation コンポーネントを使用して、ナビゲーション グラフを定義します。
  • Compose のデスティネーションを使用して、Compose 内の NavHost でナビゲーション グラフを定義します。これは、ナビゲーション グラフ内のすべての画面がコンポーザブルである場合にのみ可能です。

したがって、Compose アプリと View アプリを組み合わせる場合は、フラグメント ベースの Navigation コンポーネントを使用することをおすすめします。その後、フラグメントはビューベースの 画面、Compose 画面、ビューと Compose の両方を使用する画面。1 回 フラグメントのコンテンツは Compose にあるため、次のステップではこれらの画面をすべて結び付けます。 すべての Fragment を削除します。

Compose コード内のデスティネーションを変更するには、階層内の任意のコンポーザブルに渡してそのコンポーザブルからトリガーできるイベントを公開します。

@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
    Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}

フラグメント内で、NavController を見つけてデスティネーションに移動することにより、Compose とフラグメント ベースの Navigation コンポーネントを橋渡しします。

override fun onCreateView( /* ... */ ) {
    setContent {
        MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
    }
}

または、NavController を Compose 階層の上から下に渡すこともできます。ただし、シンプルな関数を公開すると、再利用とテストがより簡単になります。

テスト

テストを可能にするために、コンポーザブルのデスティネーションからナビゲーション コードを分離する 各コンポーザブルを NavHost コンポーザブルとは別に独立させます。

つまり、navControllerAPI に直接渡すことはできません。 コンポーズ可能な関数を作成し、代わりにナビゲーション コールバックをパラメータとして渡します。これにより、 すべてのコンポーザブルを個別にテストできるようにします。 テスト内の navController のインスタンス。

composable ラムダによって提供されるレベルの間接性により、 Navigation のコードをコンポーザブル自体から分離します。このことは次の 2 つの方向に機能します。

  • 解析された引数のみをコンポーザブルに渡します。
  • NavController 自体ではなく、移動するコンポーザブルによってトリガーされるラムダを渡します。

たとえば Profile コンポーザブルは、 userId を入力として受け取り、 友達のプロフィール ページに移動するユーザーの署名は、

@Composable
fun Profile(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 …
}

このように、Profile コンポーザブルは Navigation とは独立して動作するため、独立してテストできます。composable ラムダは、Navigation API とコンポーザブルの間のギャップを埋めるために必要な最小限のロジックをカプセル化することになります。

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
    Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
        navController.navigate("profile?userId=$friendUserId")
    }
}

NavHost、コンポーザブルに渡されるナビゲーション アクション、個々の画面コンポーザブルをテストすることにより、アプリ ナビゲーションの要件に対応するテストを作成することをおすすめします。

NavHost のテスト

NavHost のテストを開始するには、次のナビゲーション テストを追加します。 :

dependencies {
// ...
  androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
  // ...
}

NavHost のテスト対象を設定し、navController インスタンスのインスタンスをその対象に渡します。ここでは、ナビゲーション テスト アーティファクトは TestNavHostController を提供します。アプリの開始デスティネーションと NavHost を検証する UI テストは次のようになります。

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: TestNavHostController

    @Before
    fun setupAppNavHost() {
        composeTestRule.setContent {
            navController = TestNavHostController(LocalContext.current)
            navController.navigatorProvider.addNavigator(ComposeNavigator())
            AppNavHost(navController = navController)
        }
    }

    // Unit test
    @Test
    fun appNavHost_verifyStartDestination() {
        composeTestRule
            .onNodeWithContentDescription("Start Screen")
            .assertIsDisplayed()
    }
}

ナビゲーション アクションのテスト

ナビゲーションの実装は複数の方法でテストできます。UI 要素をクリックして表示されたデスティネーションを検証する方法や、想定されるルートと現在のルートを比較する方法があります。

具体的なアプリの実装をテストする場合は、UI をクリックすることをおすすめします。このテスト方法と、個々のコンポーズ可能な関数を個別にテストする方法については、Jetpack Compose でのテスト Codelab をご覧ください。

navControllernavControllercurrentBackStackEntry)を使用して、現在の String ルートと想定されるルートを比較することで、アサーションを確認することもできます。

@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
    composeTestRule.onNodeWithContentDescription("All Profiles")
        .performScrollTo()
        .performClick()

    val route = navController.currentBackStackEntry?.destination?.route
    assertEquals(route, "profiles")
}

Compose テストの基本に関する詳しいガイダンスについては、以下をご覧ください。 Compose レイアウトのテストJetpack Compose でのテスト 説明します ナビゲーション コードの高度なテストについて詳しくは、ナビゲーションをテストするのガイドをご覧ください。

詳細

Jetpack Navigation の詳細については、Navigation コンポーネント スタートガイドを参照するか、Jetpack Compose ナビゲーション Codelab をご覧ください。

さまざまな画面サイズ、向き、フォーム ファクタに適応するようにアプリのナビゲーションを設計する方法については、レスポンシブ UI のナビゲーションをご覧ください。

より高度な Compose ナビゲーション実装を Google Cloud で ネストされたグラフやボトム ナビゲーション バーなどの概念を含む、モジュール化されたアプリ GitHub の Now in Android アプリをご覧ください。

サンプル