適切な「戻る」ナビゲーションを提供する

ユーザーは「戻る」ナビゲーション を使用して画面を遡ります。ほとんどの Android デバイスには、[戻る] ボタン(物理ボタン、ソフトウェア ボタン、ジェスチャー)があります。通常、アプリに [戻る] ボタンを追加する必要はありません。ただし、互換モードの Android Automotive OS(AAOS)デバイスでは、システムの [戻る] ボタンが使用されます。これによりナビゲーションが処理されるため、独自のボタンを追加する必要はありません。詳しくは、 AAOS 互換モードをご覧ください。

Android は、ユーザーがアプリ内を移動する際、デスティネーションのバックスタック を保持します。これにより、通常は [戻る] ボタンを押すと、以前のデスティネーションに適切に移動できます。ただし、可能な限り最高のユーザー エクスペリエンスを提供するために、アプリ独自の [戻る] 動作を実装する必要性が生じることがあります。たとえば、WebView を使用している場合、デフォルトの [戻る] ボタンの動作をオーバーライドして、アプリ内の前の画面ではなく、ウェブ閲覧履歴をさかのぼれるようにできます。

Compose でカスタムの「戻る」ナビゲーションを実装する

Jetpack Compose では、カスタムの「戻る」ナビゲーションを BackHandler コンポーザブルを使用して処理できます。

Navigation Compose を使用する場合、通常は NavController.navigateUp() または NavController.popBackStack() を使用して、バックスタック内の前の画面に移動します。ただし、BackHandler は、ユーザーがシステムの [戻る] ボタンを押したときや「戻る」ジェスチャーを使用したときにカスタム動作を実装する場合に便利です。たとえば、アプリに WebView を表示している場合、ユーザーが システムの [戻る] ボタンを押したときに閲覧履歴を遡れるようにすることができます。

基本的な使用方法

カスタム状態(パネルを閉じるなど)を制御するために「戻る」イベントをインターセプトする方法の例を次に示します。

@Composable
fun MyScreen() {
    var isExpanded by remember { mutableStateOf(false) }

    // Intercept the back button or gesture only when isExpanded is true
    BackHandler(enabled = isExpanded) {
        // Collapsing the content disables this handler, so it does not
        // receive the next back event.
        isExpanded = false
    }

    if (isExpanded) {
        Text("Expanded content. Go back to collapse it.")
    } else {
        Button(onClick = { isExpanded = true }) {
            Text("Expand")
        }
    }
}

コンポーザブル ツリーのインターセプト

コンポーザブル ツリーの異なるレベルで複数の BackHandler コンポーザブルが有効になっている場合、最も内側のコンポーザブルのみが「戻る」イベントをインターセプトします。

View でカスタムの「戻る」ナビゲーションを実装する

ComponentActivity は、FragmentActivity と AppCompatActivity の基本クラスです。このクラスを使用すると、[戻る] ボタンの動作を OnBackPressedDispatcher を使用して制御できます。 OnBackPressedDispatcher は、getOnBackPressedDispatcher() を呼び出すことで取得できます。

The OnBackPressedDispatcher は、[戻る] ボタンのイベントを 1 つ以上の OnBackPressedCallback オブジェクトにディスパッチする方法を制御します。OnBackPressedCallback のコンストラクタは、初期有効状態のブール値を受け取ります。コールバックが有効になっている場合(たとえば、isEnabled() が true を返す場合)にのみ、ディスパッチャはコールバックの handleOnBackPressed() を呼び出して [戻る] ボタンのイベントを処理します。有効状態を変更するには、 setEnabled() を呼び出します。

コールバックは、addCallback メソッドを使用して追加されます。LifecycleOwner を受け取る addCallback() メソッドを使用します。これにより、 OnBackPressedCallback が LifecycleOwner が Lifecycle.State.STARTED の場合にのみ追加されます。また、アクティビティは、関連付けられた LifecycleOwner が破棄されると、登録済みのコールバックを削除します。これにより、メモリリークを防ぎ、アクティビティよりも有効期間が短いフラグメントや他のライフサイクル オーナーでの使用に適しています。

コールバック実装の例を以下に示します。

class MyFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // This callback will only be called when MyFragment is at least Started.
        val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
            // Handle the back button event
        }

        // The callback can be enabled or disabled here or in the lambda
    }
    ...
}

addCallback() を使用して複数のコールバックを指定できます。 その場合、コールバックは追加された順序とは逆の順序で呼び出されます。つまり、最後に追加されたコールバックが最初に [戻る] ボタンのイベントを処理する機会を与えられます。たとえば、one、two、three という名前の 3 つのコールバックを順番に追加した場合、それぞれ three、two、one の順序で呼び出されます。

コールバックは、 「チェーン オブ レスポンシビリティ」 パターンに従います。チェーン内の各コールバックは、前のコールバックが有効になっていない場合にのみ呼び出されます。つまり、上記の例では、コールバック three が有効になっていない場合にのみコールバック two が呼び出されます。コールバック one は、コールバック two が有効になっていない場合にのみ呼び出されます。このパターンは、追加のコールバックにも適用されます。

addCallback() を使用して追加する場合、 LifecycleOwner が Lifecycle.State.STARTED 状態になるまで、コールバックは責任の連鎖に追加されません。

一時的な変更の場合は、コールバックの有効状態を変更することを強くおすすめします。これにより、前述の順序が維持されます。これは、複数の異なるネストされたライフサイクル オーナーにコールバックが登録されている場合に特に重要です。

コールバックを完全に削除する場合は、 remove() を呼び出します。 通常、これは必要ありません。コールバックは、関連付けられた LifecycleOwner が 破棄されると自動的に 削除されるためです。