発生した事象アプリのビルドは完璧です。Gradleの同期も正常(グリーン)です。しかし、ナビゲーション用のボタンをタップした瞬間、アプリが消え去ります。Logcatを確認すると、次のような赤いテキストの壁が見つかるはずです: java.lang.IllegalStateException: CompositionLocal LocalNavController not provided.
このエラーは、Jetpack Compose版の NullPointerException と言えるものです。Composableが環境から NavController を取得しようとしたものの、何も見つからなかった場合に発生します。本質的には、まだ道具箱に入れていない道具を使おうとしている状態です。
要約:30秒でできる修正カスタムの LocalNavController を使用する場合は、UIを CompositionLocalProvider でラップする必要があります。これは通常、MainActivity のルートで行います。
val navController = rememberNavController()
CompositionLocalProvider(LocalNavController provides navController) {
// アプリのコンテンツ
MainScreen()
}
なぜこのエラーが発生するのかCompositionLocal は、UIツリーの背後でデータを下層に渡すための手段だと考えてください。これにより、一つのボタンに到達させるためだけに navController を10個もの異なる関数の引数として渡し続ける「バケツリレー(prop-drilling)」という退屈な作業を回避できます。
クラッシュは LocalNavController.current が呼び出された際に、親階層にプロバイダー(Provider)が欠落していると発生します。これは通常、以下の3つのシナリオに起因します。
- ルートの欠落:
staticCompositionLocalOfは定義したものの、NavHostをプロバイダーでラップし忘れている。- 独立したPreview: Android StudioのPreviewは隔離された環境で動作します。MainActivityの存在を知らないため、そこで定義したコントローラーを参照できません。- 手動定義のミス: デフォルト値が指定されていないためにerror()ブロックを実行するようにローカル変数を初期化しており、それがトリガーされている。## ステップバイステップの解決策### 1. グローバルルートの設定多くの開発者がこの問題に直面するのは、トップレベルでプロバイダーが不足しているためです。MainActivity.ktを開き、CompositionLocalProviderがナビゲーショングラフ全体をラップしていることを確認してください。アプリのライフサイクル中にコントローラーのインスタンスが変更されることは稀であるため、ここではstaticCompositionLocalOfを使用するのが最適です。
// 1. Localを定義(通常は NavGraph.kt ファイル内)
val LocalNavController = staticCompositionLocalOf<NavHostController> {
error("NavControllerが見つかりません!")
}
// 2. コンテンツをラップする
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) {
setContent {
val navController = rememberNavController()
CompositionLocalProvider(LocalNavController provides navController) {
AppNavigation()
}
}
}
}
}
2. Previewにおける「レンダリングの問題」の修正アプリは実機で動作するのに、Previewタブにグレーのボックスが表示される場合は、モック(模擬)プロバイダーが必要です。Previewには独自のローカルコンテキストが必要です。静的なPreview内では実際に画面遷移を行うわけではないため、コンパイラを満足させるには単純な rememberNavController() だけで十分です。
@Preview(showBackground = true)
@Composable
fun MyScreenPreview() {
val mockController = rememberNavController()
CompositionLocalProvider(LocalNavController provides mockController) {
MyScreen()
}
}
3. NavHostへのスコープ限定コントローラーをグローバルにしたくない場合は、NavHost だけをラップすることも可能です。ただし、注意してください。この特定のブロックの外側で LocalNavController.current を呼び出す Composable は、依然として即座にアプリをクラッシュさせます。
確認チェックリスト最初の画面が読み込まれたからといって、修正されたと思い込まないでください。以下の3つのチェックを行ってください。
- コールドスタート: アプリのプロセスを終了して再起動します。最初の
startDestinationが問題なく読み込まれることを確認してください。- 3レベルテスト: UIの少なくとも3階層深くまでナビゲートしてください。最も深いレベルでLocalNavController.currentが動作すれば、階層は正しく構成されています。- Previewのレンダリング: Android Studioのデザインビューを確認してください。「Render Problem」のオーバーレイが消えていれば、モックプロバイダーは機能しています。## プロのヒント:パフォーマンスの重要性CompositionLocalを使用する場合、NavControllerにはstaticCompositionLocalOfを優先的に使用してください。標準のcompositionLocalOfとは異なり、静的(static)バージョンはすべてのコンシューマー(利用者)を追跡しません。NavControllerのインスタンスは通常一度作成されるだけで差し替えられないため、静的バージョンを使用することで、アプリ全体での不要な再コンポーズ(recomposition)のオーバーヘッドを削減できます。

