Jetpack をサポートする Navigation コンポーネント アプリを作成する。コンポーザブル間を移動するには Navigation コンポーネントのインフラストラクチャと 説明します。
設定
Compose をサポートするには、アプリ モジュールの
build.gradle
ファイル:
Groovy
dependencies { def nav_version = "2.8.0" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" implementation("androidx.navigation:navigation-compose:$nav_version") }
始める
アプリにナビゲーションを実装する場合は、ナビゲーション ホストを実装します。 グラフ、コントローラなどがあります詳細については、ナビゲーションの概要をご覧ください。
NavController を作成する
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
の引数を取得するには、
データレイヤーにアクセスする場合は、ViewModel
の SavedStateHandle
を使用します。
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.1" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.1") } 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
の呼び出しに接続するためにもアイテムのルートが使用されます。saveState
と restoreState
のフラグを使用すると、ボトム ナビゲーション アイテムを切り替えるときに、そのアイテムの状態とバックスタックが正しく保存され、復元されます。
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 を削除します。
フラグメントに Navigation を使用して Compose から移動する
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
コンポーザブルとは別に独立させます。
つまり、navController
を API に直接渡すことはできません。
コンポーズ可能な関数を作成し、代わりにナビゲーション コールバックをパラメータとして渡します。これにより、
すべてのコンポーザブルを個別にテストできるようにします。
テスト内の 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 をご覧ください。
navController
(navController
の currentBackStackEntry
)を使用して、現在の 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 アプリをご覧ください。
サンプル
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- Compose のマテリアル デザイン 2
- Jetpack Navigation を Navigation Compose に移行する
- 状態をホイスティングする場所