2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

rememberPermissionStateでパーミッションリクエスト時にダイアログが表示されなかったことを検出する

Posted at

Jetpack ComposeでAndroidのパーミッションをリクエストする場合、accompanistのrememberPermissionStateを利用するのが便利です。

パーミッションリクエストでは、ユーザーが「許可しない」を2回選択した場合、リクエストしてもダイアログが表示されず、即座に失敗してしまいます。ユーザーが「許可しない」と選択したのだからそれ以上要求すべきではないというのは分かるのですが、その場合はユーザに別の案内をする必要があるのに、これを直接判定する手段が提供されていません。これはJetpack Composeだからとか、accompanistの機能不足、ということではなく、Androidシステムが提供していません。
その回避策としてActivityResultContractを直接使う場合の方法は以下で解説しています。

ではrememberPermissionStateではどうやれば良いのかというのが今回の話題です。(rememberPermissionStateは内部的にはActivityResultContractが使われています)

ダイアログが表示されなかったことの判定方法

そもそも、直接的な検出方法が提供されていないのにどうやって検出するか、ですが
まずはshouldShowRequestPermissionRationaleの戻り値(permissionState.status.shouldShowRationale)の変化を観測します。
これは、パーミッションリクエスト時により詳細な説明をせよというフラグですが、ユーザーが何回「許可しない」を選択したかで変化します。
0回ならfalse、1回ならtrue、2回ならfalse、と変化します。
リクエスト前と後の変化をまとめると以下になります。

許可しない回数 リクエスト前 ダイアログの選択 コールバック時
0回 false キャンセル false
0回 false 許可しない true
1回 true キャンセル true
1回 true 許可しない false
2回 false ダイアログが表示されない false

すなわち、リクエスト前も後もfalseの場合、一度も「許可しない」を選択されていない時にダイアログがキャンセルされたか、ダイアログが表示されなかったか、の2択に絞ることができます。

2択のうち後者であることを判定するには、shouldShowRequestPermissionRationaleがtrueになったことがないと判定できれば確実ですが、それには永続化保存が必要になるため、あまりお手軽な方法とは言えません。

ここでは、ダイアログが表示されたユーザー操作が行われた場合と、ダイアログが表示されず即座に終了してしまった場合では、経過時間に大きな隔たりがある。と想定し、結果が返ってくるまでが一定時間以下である場合にダイアログが表示されなかったと判定することとします。
この方法はもちろん完璧ではなく、誤判定の可能性はありますが、表示されなかったと判定するほど短い時間しか表示されなかった場合なので、表示されなかった場合のダイアログが出てきてもそれほど違和感を覚えられないので十分かなと考えています。

rememberPermissionStateWrapper を作る

前述の方法で判定できるにしても、リクエストの度に実装するのは面倒なので、ダイアログが表示されなかった場合の判定機能を持ったラッパーメソッドを作ってみます

判定方法は決まっているのでその通り実装して以下のようになるかと思います。
ダイアログが表示されなかったと判定する時間として300msを設定していますが、何か根拠がある訳でありません。経験上このぐらいの時間で判定すればうまくいくというだけのことです。

private const val ENOUGH_DURATION = 300L

@Composable
fun rememberPermissionStateWrapper(
    permission: String,
    onPermissionResult: (granted: Boolean, failedToShowDialog: Boolean) -> Unit = { _, _ -> },
): PermissionState {
    val activity = LocalActivity.current!!
    var shouldShowRationaleBefore = false
    var start: Long = 0
    val permissionState = rememberPermissionState(permission) {
        val elapsedEnoughTime = System.currentTimeMillis() - start > ENOUGH_DURATION
        val shouldShowRationaleAfter = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
        val failedToShowDialog = !(shouldShowRationaleBefore || shouldShowRationaleAfter || elapsedEnoughTime)
        onPermissionResult(it, failedToShowDialog)
    }
    return object : PermissionState {
        override val permission: String = permissionState.permission
        override val status: PermissionStatus = permissionState.status
        override fun launchPermissionRequest() {
            shouldShowRationaleBefore = permissionState.status.shouldShowRationale
            start = System.currentTimeMillis()
            permissionState.launchPermissionRequest()
        }
    }
}

rememberPermissionStateのコールバックはgrantedの引数一つですが、第二引数でダイアログ表示がなされなかったか表示されたかが渡されるようにしています。


以上です

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?