はじめに
ModalBottomSheet の中に NavHost を置いてマルチ画面の構成にしている場合、Android 16+(targetSdk 36)の端末でバックジェスチャーを行うと、NavHost が1画面戻るのではなくシート全体が閉じてしまうというバグが発生しました。
原因
先に原因を紹介しますが、
ModalBottomSheet は内部でシートを閉じるバック処理を持っていて、Android 16+ ではこれが BackHandler や NavHost の自動ポップより先に動いてしまいます。
また、ModalBottomSheet は独立した Dialog ウィンドウとして表示されるため、content の外に置いた BackHandler はシートが前面にいる間は呼ばれません。
解決法
shouldDismissOnBackPress = false を設定しつつ、BackHandler を content 内・NavHost の後 に置きます。
ModalBottomSheet(
onDismissRequest = { hideAndDismiss() },
properties = ModalBottomSheetProperties(shouldDismissOnBackPress = false),
) {
NavHost(navController = innerNavController, ...) { /* 各画面 */ }
// NavHost 2.8+ はバックスタックを自力でポップするので、ここではルート画面でシートを閉じるだけでいい
val currentEntry by innerNavController.currentBackStackEntryAsState()
BackHandler(
enabled = currentEntry != null && innerNavController.previousBackStackEntry == null,
) {
hideAndDismiss()
}
}
previousBackStackEntry は Compose の state ではないので、currentBackStackEntryAsState() を同じスコープで読んで recompose
を引き起こす必要があります。これを忘れると enabled が初回コンポーズ時の値のまま更新されません。
おまけ
最初に shouldDismissOnBackPress = false
だけ設定したら、ネスト画面からのバックは直ったもののルート画面でシートが閉じなくなりました。
次に BackHandler を ModalBottomSheet の外に条件付きで置く方法を試したら、ログを仕込んで確認するとコールバックが一度も呼ばれていませんでした(Dialog
ウィンドウが前面にいる間は Activity 側の dispatcher にバックが届かないため)。
結局、両方セットで対応するのが正解でした。