
Android 開発者を長年悩ませてきた「画面回転」や「プロセス死」に伴う状態保存。SavedInstanceState や SavedStateHandle と格闘する日々は、もう過去のものになろうとしています。
Jetpack Navigation3 のソースコードを読み解くと、新しく登場した NavKey という仕組みが、Compose 時代の状態保存をいかにスマートに変革しようとしているかが見えてきました。
🤔 1. NavKey:単なる「マーカー」ではない、型安全の要
/**
* Marker interface for keys.
*
* Objects and classes that extend this class must be marked with the [Serializable] annotation in
* order to be saved with by the [rememberNavBackStack] function.
*
* This class is required because [Serializable] is only an annotation and does not provide a way to
* link classes marked with the annotation together and provide a serializable that works with all
* of them, resulting it making it impossible to properly save and restore.
*/
public interface NavKey
Navigation3 でバックスタックを管理する rememberNavBackStack を使う際、避けて通れないのが NavKey インターフェースの実装です。一見すると中身のないマーカーインターフェースですが、これこそが 「型安全な自動保存」 の鍵を握っています。
従来の Navigation では、引数の受け渡しや状態保存の裏側で Bundle が使われてきました。しかし、Bundle は何でも入る反面、型安全性が欠け、実行時のエラーを招きやすいという課題がありました。
/**
* Provides a [NavBackStack] that is automatically remembered in the Compose hierarchy across
* process death and configuration changes.
*
* This overload **does not take a [SavedStateConfiguration]**. It relies on the platform default:
* on **Android**, state is saved/restored using a **reflection-based serializer**; on **other
* platforms this will fail at runtime**. If you target non-Android platforms, use the overload that
* accepts a [SavedStateConfiguration] and register your serializers explicitly.
*
* ### When to use this overload
* - You are on **Android only** and want a simple API that uses reflection under the hood.
* - Your back stack elements use **closed polymorphism** (sealed hierarchies) or otherwise work
* with Android’s reflective serializer.
*
* ### Serialization requirements
* - Each element placed in the [NavBackStack] must be `@Serializable`.
* - For **closed polymorphism** (sealed hierarchies), the compiler knows all subtypes and generates
* serializers; Android’s reflection will also work.
* - For **open polymorphism** (interfaces or non-sealed hierarchies):
* - On Android, the reflection path can handle subtypes without manual registration.
* - On non-Android, this overload is **unsupported**; use the configuration overload and
* register all subtypes of [NavKey] in a [SerializersModule].
*
* @sample androidx.navigation3.runtime.samples.rememberNavBackStack_withReflection
* @param elements The initial keys of this back stack.
* @return A [NavBackStack] that survives process death and configuration changes on Android.
* @see NavBackStackSerializer
*/
@Composable
public fun rememberNavBackStack(vararg elements: NavKey): NavBackStack<NavKey> {
return rememberSerializable(
serializer = NavBackStackSerializer(elementSerializer = NavKeySerializer())
) {
NavBackStack(*elements)
}
}
NavKey は、バックスタックに入るすべての要素が「シリアライズ可能であること」をコンパイル時に保証するための型上の制約として機能します。
🤔 2. なぜ「Serializable アノテーション」だけでは不十分か
Kotlin Serialization を使っているなら、@Serializable だけで十分だと思うかもしれません。しかし、ソースコードのコメントには重要な洞察が記されています。
Serializable はあくまでアノテーションであり、クラス同士をコード上でリンクさせる手段を提供しない。そのため、すべての要素を適切に保存・復元することが不可能になる。
NavKey という共通のインターフェースを介することで、Navigation 側は「このバックスタックの中身はすべて、共通の手順でシリアライズできる仲間である」と認識できるようになります。これが、「何も考えなくても状態が復元される」 という体験の裏側にあるロジックです。
🤔 3. Android 限定の「リフレクション」がもたらす魔法
Navigation3 の rememberNavBackStack には、Android 開発者にとって非常に強力な恩恵があります。
Android プラットフォーム上では、リフレクションベースのシライライザーがデフォルトで動作します。これにより、開発者はシリアライザーを個別に登録する手間(ボイラープレート)から解放されます。
- 開発者:
NavKeyを継承し、@Serializableを付ける。 - Navigation3: process death の際、リフレクションを使ってバックスタックをまるごと保存し、復帰時に自動で復元する。
これこそが、「さらば SavedInstanceState」と言える最大の理由です。
🤔 4. Navigation3 時代の実装スタイル
これからの Compose 開発では、以下のように NavKey を実装した sealed interface を定義するのが標準になるでしょう。
@Serializable
sealed interface Screen : NavKey {
@Serializable
data object Home : Screen
@Serializable
data class UserProfile(val userId: String) : Screen
}
// Composable 内での利用
val backStack = rememberNavBackStack(Screen.Home)
この実装だけで、userId を含めた遷移状態が、OSによるメモリ回収の後でも完璧に復元されます。もはや Bundle を意識するシーンはほとんどなくなるはずです。
🤔 まとめ
Navigation3 における NavKey の導入は、Google が掲げる開発思想の現れのように感じます。
内部の複雑なシリアライズ処理を NavKey という一つのインターフェースに集約し、開発者には最小限の実装(アノテーションと継承)だけで最大限の恩恵(自動状態保存)を与える。
「状態保存に怯える開発」はもう終わりです。Navigation3 を武器に、より本質的な UI 実装に集中していきましょう。
Related Categories : Android・Google・JetBrains・JetpackCompose・Kotlin・Trending