ランタイムパーミッションならDexterも良い



 Androidのランタイムパーミッション周りの処理で使うライブラリといえばPermissionsDispatcherですが、Dexterも使いやすくて便利なので簡単な利用例を交えて紹介します。

### 追記(2022/07/05)
**現在アーカイブされたライブラリとなっており、新しいパーミッションには対応できなくなってきていますので、今後は純正のパーミッション実装をすることをおすすめします**

### 説明
https://github.com/Karumi/Dexter

以下公式説明から引用
> Dexterは、実行時に権限を要求するプロセスを簡素化するAndroidライブラリです。
> アクセス許可コードをアクティビティから解放し、そのロジックを好きな場所に記述できるようにします。

### 例
Dexterオブジェクトを作成し、取得したい権限とランタイムパーミッションでのユーザー操作後の処理を定義します。
下記は画面上のボタンを押した時に位置情報権限（ACCESS_FINE_LOCATION, ACCESS_COURSE_LOCATION）を取得する処理を行うことを想定した例です

```kotlin: 
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側にパーミッション操作後の処理を定義するようにメソッド化しました

```kotlin:パーミッション処理部分
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に入る
    - 過去に拒否された

```kotlin:UI側
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オブジェクトは使えません。

```kotlin:権限が欲しい理由ダイアログ(MaterialDialogを使用)

    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を使用します

```kotlin
        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と比べるとコミュニティの規模は相当な差がありそうです。