X

Jetpack Compose: AlertDialogコンポーザブルを使う

今回のJetpack ComposeサンプルはAlertDialogコンポーザブル( androidx.compose.material.AlertDialog)です。Androidアプリのアラートダイアログはアプリ利用者に詳細情報を提供し、アクション実行前に利用者へ確認するなど現在の操作の中断や決定、新たな意思決定を促すUIパターンです。

Jetpack ComposeのAlertDialogコンポーザブル

表示すると画面の上にダイアログが重なるため、利用者は作業を中断せざるを得ません。どうしても確認してほしい緊急性の高いエラー情報などがAlertDialogコンポーザブルの出番です。

AlertDialogを表示する

単純なAlertDialogコンポーザブル(エラー発生の例)

AlertDialogコンポーザブルの引数はダイアログのタイトル、説明文、Confirmボタンが最小構成です。ダイアログの表示/非表示状態する変数と一緒に使います。アプリの処理を継続できないエラー、購入処理が困難な品切れ時の詳細など継続できなくて中断せざるを得ない状況に便利です。

@Composable
fun SimpleAlertDialog() {
    val openDialog = remember { mutableStateOf(true) }

    if (openDialog.value) {
        AlertDialog(
            onDismissRequest = {   },
            title = {
                Text(text = "Network disconnected")
            },
            text = {
                Text("Please try 
                    again with the stable network conditions.")
            },
            confirmButton = {
                TextButton(
                    onClick = { // confirmをタップしたとき
                        openDialog.value = false
                    }
                ) {
                    Text("OK")
                }
            },
            dismissButton = null
        )
    }
}

MutableState<Boolean>型のopenDialogでダイアログの状態を制御し、AlertDialogコンポーザブルではタイトル(title)、説明文(text)そしてconfirmButtonを設定します。onDismissRequestとdismissButtonは後述します。またMutableStateの動作を知りたい場合は前回の記事(Jetpack Compose: Switchコンポーザブルを使うの「状態を記憶する」)を読んでみてください。

選択できるAlertDialogを表示する

キャンセルできるAlertDialogコンポーザブル

AlertDialogコンポーザブルを使って機能利用などを選択する場合、キャンセルボタンを用意します。 規約やプライバシーポリシーの同意・個人情報の取得確認などもこちらのユースケースにあたりそうですね。

@Composable
fun ConformAlertDialog() {
    val openDialog = remember { mutableStateOf(true) }

    if (openDialog.value) {
        AlertDialog(
            onDismissRequest = {   },
            title = {
                Text(text = "位置情報を使いますか?")
            },
            text = {
                Text("GPSから位置情報を取得するとタイムラインを自動記録できます。")
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        openDialog.value = false
                    }
                ) {
                    Text("使う")
                }
            },
            dismissButton = {
                TextButton(
                    onClick = { // キャンセルをタップしたとき
                        openDialog.value = false
                    }
                ) {
                    Text("キャンセル")
                }
            },
        )
    }
}

dismissButtonにはキャンセル用のTextButtonコンポーザブルを指定してクリック時のイベントをonClickで受け取ります。マテリアルデザインのベストプラクティスに基づき、タイトルは「警告」など曖昧な表現ではなく「Aしますか?」などわかりやすいワーディングにしましょう。また選択肢も「キャンセル」「OK」ではなく「キャンセル」「使う」といったアクションを選びましょう(利用者の誤解が減ります)。※状態が変わらない、また操作のやり直しがきく(再度選べばすむだけ。データが編集されたり消えたりする場合は別)という状況でのみCANCEL / OKの出番です。

ダイアログの外側のタップを抑止したい

赤枠のみ反応できるようにしたい場合

ダイアログの外側に不意に触れてキャンセルしてしまった…というケースを避けたい場合、AlertDialogコンポーザブルの引数のうちonDismissRequestで対応できます。

@Composable
fun SimpleAlertDialog() {
    val openDialog = remember { mutableStateOf(true) }

    if (openDialog.value) {
        AlertDialog(
            onDismissRequest = { /* からっぽでOK */  },
            title = {
                Text(text = "Network disconnected")
            },
            ...省略...
        }
...省略...
}

onDismissRequestの処理を空にしておけばダイアログは非表示になりません。ダイアログの外側をタップしたときにもAlertDialogコンポーザブルを非表示にしたい場合・非表示を許可する場合はサンプルコードを次のように変更します。

@Composable
fun SimpleAlertDialog() {
    val openDialog = remember { mutableStateOf(true) }

    if (openDialog.value) {
        AlertDialog(
            onDismissRequest = {
                openDialog.value = false
            },
            ...省略...
}

onDissmissRequestの処理中にopenDialog.valueをfalseで書き換えています。これで状態変更が伝わり、再描画の際にはAlertDialogコンポーザブルが非表示になります(ほかにキャンセル処理が必要な場合、サンプルコードであればどこに書いてもいいのですが、onDismissRequestのなかにロジックが混じってしまうのは避けたいところです。状態変更を監視しているオブザーバー側でやるといいですね。そのあたりの構造も別の記事で取り上げたいと考えています)。

ダイアログのベストプラクティス

confirmButtonは「OK」ではなくアクション(「送信」など)を表示する

Jetpack ComposeのAlertDialogコンポーザブルに限った話題ではありませんが ダイアログはユーザー操作を中断する強制力のあるUIです。実務で利用する場合は安易なYES/NOではなくマテリアルデザインガイドラインを参考に適切なアクションと文言を使うように工夫してみてください。

AlertDialogコンポーザブルの引数

@Composable
fun AlertDialog(
    onDismissRequest: () -> Unit,
    confirmButton: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    dismissButton: @Composable (() -> Unit)? = null,
    title: @Composable (() -> Unit)? = null,
    text: @Composable (() -> Unit)? = null,
    shape: Shape = MaterialTheme.shapes.medium,
    backgroundColor: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    properties: DialogProperties = DialogProperties()
) {
  • onDismissRequest:ダイアログの外側をタップしたとき・戻るボタンを押したときに呼び出される(dismissButtonタップでは呼び出されない)。
  • confirmButton:確認ボタンを設定する。onClickイベントは自分で記述すること。
  • modifier:レイアウトへ反映するmodifier
  • dismissButton:ダイアログを閉じるボタン。nullの場合、非表示。
  • title:タイトル。Typography.h6で表示されます。
  • text:本文。Typography.body1で表示されます。
  • shape:ダイアログの形状を指定
  • backgroundColor:背景色の指定
  • contentColor:ダイアログの子要素へ提供するコンテンツカラー
  • properties:プラットフォーム固有のプロパティ情報

androidx.compose.materialパッケージに含まれているDialogコンポーザブルは記事作成時点でAlertDialogコンポーザブルのみです。マテリアルデザインでは情報提供や通知の整理が進んでいてスナックバー、バナー、ダイアログの3種類をつかったユーザーコミュニケーションが情報設計の基本となっています。現時点ではマテリアルデザインに準拠した形でカスタマイズしようとしても足りないパーツも多いので今後の進化も期待できそうです。

今回の記事はこれでおしまいです。お疲れさまでした。

mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org