”PermissionDispatcher”でAndroidパーミッションをラクに実装！

# 概要

Androidアプリにて、端末の様々な情報へのアクセスや機能の利用をユーザーに伝え、そのアクセスや機能の許可を促すための仕組み、「パーミッション」。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211499/a69fa7b6-6894-a883-a4ee-3423d575adae.png" width="400">

ストレージ、カメラ等にアクセスするアプリは、このパーミッション機能を必ず実装する必要があります。で、そのパーミッション機能、ベタで書いたら、案外コード量が多く複雑なものになりがちなのですが、"PermissionDispatcher" というライブラリを使うと、カンタンに実装できることができます。

## カメラ・外部ストレージ書き込みのパーミッション実装
Android アプリで頻出しそうなケースであること、そしてkotlinでいいサンプルが見当たらなかったのカメラ(android.permission.CAMERA)と外部ストレージ書き込み(android.permission.WRITE_EXTERNAL_STORAGE) のパーミッションチェックをPermissionDispatcher で実装しました。

[https://github.com/rochefort8/PermissionCheck-android-sample](
https://github.com/rochefort8/PermissionCheck-android-sample)

# 環境

上記GitHub の動作・開発環境です。

### Host
MacBook Pro (Retina, 13-inch, Mid 2014)
macOS High Sierra 10.13.6

### Android Project
* Android Studio : 3.3.1
* Grade : 4.10.1
* Android Plugin version : 3.3.2
* Target SDK version : 28

### 動作確認
* Xperia XZ1 (Android 8)
* Nexus 5 (Android 6.0.1)
* Genymotion Google Pixel
 
# 手順

以下の記事に手順、機能詳細が書いてあります。

[【Android】 パーミッション判定の複雑なコードを解決！PermissionsDispatcherライブラリを試してみた](https://dev.classmethod.jp/smartphone/android/permissions-dispatcher/)

以下、この記事に沿って作成したときの手順です。

* 新規プロジェクト作成
略

* PermissionDispatcher ライブラリ利用のための記述

app の方のbuild.gradleに以下を追記してSyncする。

```build.gradle

...
// 追記
apply plugin: 'kotlin-kapt'
 
dependencies {
....
  // 以下の4行を追記
    implementation ('com.github.hotchemi:permissionsdispatcher:3.3.1'){
        exclude module: "support-v13"
    }
    kapt "com.github.hotchemi:permissionsdispatcher-processor:3.3.1"
}
```
```AndroidManifest.xml

    <!-- Permission -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

* 新しいActiivty ”PermissionCheckActivity”を作成
ここにPermission Check を実装。

* Permission Check で使う文字列の定義

```strings.xml
    <string name="alert_diaglog_message_for_permission">アプリがこの操作を実行するには次の権限の許可が必要です。\n
        カメラ\nストレージ\n\n権限を許可するには、[アプリ情報]→[許可]に移動してください。</string>
    <string name="button_name_app_info">アプリ情報</string>
```

* Launcher Activity をPermisshinCheckActivity にする

```AndroidManifest.xml

// アクティビティを「入れ替え」
        <activity android:name=".MainActivity">
        </activity>
        <activity android:name=".PermissionCheckActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
```

* PermissionCheckActivity.kt 実装

```PermissionCheckActivity.kt
package com.rochefort10.permissiondispatcher_sample

import android.Manifest
.....

@RuntimePermissions
class PermissionCheckActivity : AppCompatActivity() {

    var isWriteExternalStorageAllowed = false
    var isCameraAllowed = false
    val REQUEST_CODE_MAGIC = 1212

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        startPermissionCheck()
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        // NOTE: delegate the permission handling to generated function
        onRequestPermissionsResult(requestCode, grantResults)
    }

    // ---------- WRITE_EXTERNAL_STORAGE ----------
    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    fun showWriteExternalStorage() {
        isWriteExternalStorageAllowed = true
        showCameraWithPermissionCheck()
    }

//    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
//    fun showWriteExternalStoraga(request: PermissionRequest) {
//        showRationaleDialog("Reason to use external storage.",request)
//    }

    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    fun onWriteExternalStorageDenied() {
        isWriteExternalStorageAllowed = false
        showCameraWithPermissionCheck()
    }

    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    fun onWriteExternalStorageNeverAskAgain() {
        isWriteExternalStorageAllowed = false
        showCameraWithPermissionCheck()
    }

    // ----------- CAMERA ----------
    @NeedsPermission(Manifest.permission.CAMERA)
    fun showCamera() {
        isCameraAllowed = true
        startNextActivity()
    }

//    @OnShowRationale(Manifest.permission.CAMERA)
//    fun showRationaleForCamera(request: PermissionRequest) {
//        showRationaleDialog("Reason to use camera access.",request)
//    }

    @OnPermissionDenied(Manifest.permission.CAMERA)
    fun onCameraDenied() {
        isCameraAllowed = false
        startNextActivity()
    }

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    fun onCameraNeverAskAgain() {
        isCameraAllowed = false
        startNextActivity()
    }

    private fun startPermissionCheck() {
        showWriteExternalStorageWithPermissionCheck()
    }

    private fun startNextActivity() {

        if ((isCameraAllowed == true) && (isWriteExternalStorageAllowed == true)) {

            // Every permission are set, moving to the next step
            val intent = Intent(this, MainActivity::class.java)
            startActivity(intent)
            finish()
        } else {

            // Not enough permissions, finish with some message
            if (!isCameraAllowed)
                Log.d("CAM", "No")
            if (!isWriteExternalStorageAllowed)
                Log.d("STO", "No")

            AlertDialog.Builder(this)
                .setPositiveButton(android.R.string.cancel) { _, _ ->
                    finish()
                }
                .setNegativeButton(R.string.button_name_app_info) { _, _ ->
                    val uriString = "package:$packageName"
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse(uriString))
                    startActivityForResult(intent, REQUEST_CODE_MAGIC)
                }
                .setCancelable(false)
                .setMessage(R.string.alert_diaglog_message_for_permission)
                .show()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)

        if (requestCode == REQUEST_CODE_MAGIC) {
            // Start again
            startPermissionCheck()
        }
    }
}
```

# 情報源
* [PermissionDispatcher github repository](https://github.com/permissions-dispatcher/PermissionsDispatcher)

* [Android developer guide - Permissions overview](https://developer.android.com/guide/topics/permissions/overview)

* [Androidのパーミッションと6.0での処理の流れを今さら説明してみる](https://qiita.com/imaizume/items/178cb540b697cd9419fd)