Jetpack Navigation3の感想
Google I/O 2025でゼロから構築されたナビゲーションライブラリが発表されました。これまでとの違いや、気になった実装などを確認してみました。
まだまだalphaの段階なので、今後変更されると思います。細かい使い方などはドキュメントを確認してください。
⚠️ まだ深く理解していない状態なので、この記事では推奨されてないこともやってるかもです。
これまでとの大きな違い
これまでのNavigationとの大きな違いは、Navigation3では画面遷移をBackStackを操作して行う点です。
これまでは navController.navigate
などで画面遷移をしていて、結果としてBackStackが内部的に変更されていました。なので、直接BackStackを操作することはなかったと思います。
それに対してNavigation3ではBackStackを操作した結果で、画面遷移が行われます。
BackStackの操作
Navigation3では以下のようにBackStackを作成しますが、中身自体は単純なListになっています。
val backStack = rememberNavBackStack(ProductList)
画面遷移するときはBackStackに追加したり、削除したりすることで実現することになります。
// 次の画面へ遷移
backStack.add(ProductDetail(id = "test"))
// 前の画面に戻る
backStack.removeLastOrNull()
要はListを操作することで自由にBackStackを変更することができます。
なので、以下のようにBackStackの途中に画面を差し込むことができます。(今のところ、これが良いか悪いか不明ですが…)
backStack.add(1, SomeScreen)
これまでのNavigationではこういうことは出来なかったので、かなり柔軟なことができそうです。
他にも複数のBackStackを管理することもできるので、 NavigationBar
なども直感的に管理することが出来ます。
val backStack1 = rememberNavBackStack(Hoge)
val backStack2 = rememberNavBackStack(Foo)
var selectedIndex by remember { mutableIntStateOf(0) }
if (selectedIndex == 0) {
NavDisplay(
backStack = backStack1,
// ...
)
} else {
NavDisplay(
backStack = backStack2,
// ...
)
}
引数の受け渡し
引数についてもこれまでとは異なり、複雑なオブジェクトもタイプセーフで安全に受け渡しできます。単純にBackStackにオブジェクトを渡すだけです。
// 画面と引数の定義
@Serializable
data class ProductDetail(val id: String) : NavKey
// 引数を渡して画面遷移
backStack.add(ProductDetail(id = "test"))
受けるときもBackStackのオブジェクトをそのまま受け取ることが出来ます。
NavDisplay(
// ...
entryProvider = entryProvider {
// ...
entry<ProductDetail> { productDetail ->
// 引数受け取る
Text("ID: ${productDetail.id}")
}
}
)
ただ、これまでのものと違って、 SavedStateHandle
から取得できないので、ViewModelのコンストラクタに渡したい場合は、HiltとAssistedInjectを使うことになるかなと思います。
entryDecorators
個人的に面白いなと思った機能です。
NavDisplay
には、 entryDecorators
という引数があります。ドキュメントではViewModelのスコープを設定するときに以下のように使用することが説明されています。
NavDisplay(
// ...
entryDecorators = listOf(
rememberSceneSetupNavEntryDecorator(),
rememberSavedStateNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
)
)
これを使うと、画面遷移するときと戻るときに処理を差し込むことが出来ます。
簡単な例としてログを表示するものを作ってみます。 NavEntryDecorator
を作ってから NavDisplay
に設定します。
// デコレーターの定義
@Composable
fun rememberLogNavEntryDecorator(): NavEntryDecorator<Any> {
return remember {
navEntryDecorator(
onPop = { key ->
// 画面から戻るとき
Log.d(TAG, "POP: $key")
}
) { entry ->
// 画面の遷移時
LaunchedEffect(Unit) {
Log.d(TAG, "ENTER: ${entry.key}")
}
// 画面を表示
entry.content(entry.key)
}
}
}
NavDisplay(
// ...
entryDecorators = listOf(
// ...
// 定義したデコレーターを設定
rememberLogNavEntryDecorator(),
),
)
こんなふうに共通の処理を実装することができます。
他にも SceneStrategy
というのもあります。こちらは大画面のList-Detailのような複数画面を処理したいときに使うことになります。
感想
画面遷移の状態をBackStackにて管理することで、以前のものよりかなりシンプルで柔軟な処理ができるようになった印象です。触ってて色々発見があって楽しかったです。
今後、色々と問題にぶつかることもあるかなと思いますが、個人的には期待しています。