Androidのランタイムパーミッション周りの処理で使うライブラリといえばPermissionsDispatcherですが、Dexterも使いやすくて便利なので簡単な利用例を交えて紹介します。
追記(2022/07/05)
現在アーカイブされたライブラリとなっており、新しいパーミッションには対応できなくなってきていますので、今後は純正のパーミッション実装をすることをおすすめします
説明
以下公式説明から引用
Dexterは、実行時に権限を要求するプロセスを簡素化するAndroidライブラリです。
アクセス許可コードをアクティビティから解放し、そのロジックを好きな場所に記述できるようにします。
例
Dexterオブジェクトを作成し、取得したい権限とランタイムパーミッションでのユーザー操作後の処理を定義します。
下記は画面上のボタンを押した時に位置情報権限(ACCESS_FINE_LOCATION, ACCESS_COURSE_LOCATION)を取得する処理を行うことを想定した例です
Dexter.withContext(context)
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(p0: MultiplePermissionsReport?) {
// パーミッションリクエストで何らかの選択がされた
}
override fun onPermissionRationaleShouldBeShown(
p0: MutableList<PermissionRequest>?, p1: PermissionToken?
) {
// 過去に許可しないが選択された 引数のPermissionTokenを持っている限り
// 権限のリクエスト状態が継続される
}
}).check()
私は以下のようにUI側にパーミッション操作後の処理を定義するようにメソッド化しました
fun checkPermission(
onPermissionChecked: (MultiplePermissionsReport?) -> Unit = { _ -> },
onShouldShowRational: (MutableList<PermissionRequest>?, PermissionToken?) -> Unit = { _, _ -> }
) {
Dexter.withContext(context)
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(p0: MultiplePermissionsReport?) {
onPermissionChecked.invoke(p0)
}
override fun onPermissionRationaleShouldBeShown(
p0: MutableList<PermissionRequest>?,
p1: PermissionToken?
) {
onShouldShowRational.invoke(p0, p1)
}
}).check()
}
下記に従ってやりたい処理を続けます
- onPermissionCheckedに入る
- 今後表示しないにチェックの上拒否された > isAnyPermissionPermanentlyDeniedがtrue
- 全ての権限が許可された > areAllPermissionGranted()がtrue
- 拒否された
- onShouldShowRationalに入る
- 過去に拒否された
binding.currentLocationButton.setOnClickListener {
locationTracker.checkPermission(
onPermissionChecked = {
// 今後表示しない
if (it?.isAnyPermissionPermanentlyDenied != false) {
locationTracker.showToast()
return@checkPermission
}
// 許可された
if (it.areAllPermissionsGranted()) {
locationTracker.requestLocation(activity ?: return@checkPermission)
return@checkPermission
}
// 拒否された
},
onShouldShowRational = { _, token ->
locationTracker.showRational(
token ?: return@checkPermission,
activity ?: return@checkPermission
)
}
)
}
拒否された後に何かしたい場合はtokenを使い回します(下記continuePermissionRequest(),
cancelPermissionRequest())
tokenはcontinueかcancelされる限り生き続けます。その間、他のDexterオブジェクトは使えません。
fun showRational(token: PermissionToken, activity: FragmentActivity) {
MaterialDialog(activity).show {
message(null, "機能を利用するためには位置情報の権限が必要です。継続して利用するために、次のウィンドウを許可してください")
positiveButton(R.string.ok) {
token.continuePermissionRequest()
}
negativeButton(R.string.cancel) {
token.cancelPermissionRequest()
Toast.makeText(
activity,
"機能を利用するためには位置情報の権限が必要です",
Toast.LENGTH_SHORT
).show()
}
}.cancelOnTouchOutside(false)
}
基本的なパーミッションリクエストの実装は上記のとおりです。
READ_EXTERNAL_STORAGEなど、単体のパーミッションを取得したい場合はMultiplePermissionsListenerではなくPermissionListenerを使用します
Dexter.withContext(context)
.withPermission(
Manifest.permission.READ_EXTERNAL_STORAGE
).withListener(object : PermissionListener {
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
// 許可された
}
override fun onPermissionDenied(response: PermissionDeniedResponse?) {
// 拒否された
if (response?.isPermanentlyDenied == true) {
// 今後表示しない
}
}
override fun onPermissionRationaleShouldBeShown(
request: PermissionRequest?,
token: PermissionToken?
) {
// 一度拒否された
// もう一度許可を求める
token.continuePermissionRequest()
}
}).check()
PermissionsDispatcherとの違い
自動生成コードを使わない
自動生成メソッドを通すと処理の見通しが立ちにくいため(個人の感想です)、Dexterオブジェクトの生成部分の一箇所に処理がまとまるのは良い点だと思います
ライフサイクルに紐づかない
onActivityResult()が現役の場合はPermissionsDispatcherを利用する場合はonResumeに自動生成メソッドを置かないようにする等の注意が必要ですが、そこらへんのパーミッション取得に関する部分以外で気にかける点は少なくなります
コードがフルJava
気になる人は気になる点かもしれません。PermissionsDispatcherと比べるとコミュニティの規模は相当な差がありそうです。