【Android】ResultAPI を使って実行時権限をリクエストしてみる

AndroidX に追加された ResultAPI を使って実行時権限をリクエストする処理を書き方をご紹介したいと思います。

## ResultAPI とは
ResultAPI とは、androidx.activity のバージョン 1.2.0-alpha02 と androidx.fragment のバージョン 1.3.0-alpha02 で追加された API です。

これを使うことで非推奨となった ```onActivityResult``` や ```onPermissionRequest``` などの処理を置き換えるとともに簡潔に書き直すことができるようになります。

今回は ```onPermissionRequest``` が実現できる実行時権限をリクエストしてその結果を受け取る処理を ResultAPI を使って書いていきたいと思います。

## 環境
Android Studio：2020.3.1 Patch 4
kotlin：1.5.31
targetSdkVersion：31
minSdkVersion：27

## 準備
まずは gradle にライブラリを追加します。

``` build.gradle

dependencies {
...

  implementation 'androidx.activity:activity-ktx:1.4.0'
  implementation 'androidx.fragment:fragment-ktx:1.4.0'
}

```

次に AndroidManifest にリクエストする権限を追加します。

``` AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.resultapisample">
    
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.CAMERA" />
  
  ...
  
</manifest>
```

レイアウトファイルのコードも記載しておきます。

``` activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent">

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/button1"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="button1"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/button2"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/button2"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="button2"
            android:layout_marginTop="20dp"
            app:layout_constraintTop_toBottomOf="@+id/button1"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
```

## 権限をリクエスト(一つ)

ResultAPI を使って一つだけ権限をリクエストしてみます。

まずはリクエスト結果を受け取る処理を ```registerForActivityResult``` メソッドを使って実装します。
第一引数には ```ActivityResultContracts.RequestPermission``` クラスを、第二引数にはコールバック関数を指定します。
コールバック関数の引数は権限を許可したか否かの結果が入る Boolean 型の変数です。

```　MainActivity.kt
    private val permissionResult = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
        Log.d("permission result", isGranted.toString())

        if (isGranted) {
            Toast.makeText(this, "位置情報が許可されました。", Toast.LENGTH_SHORT).show()
        } else {
            AlertDialog.Builder(this)
                .setMessage("この機能を使用するために位置情報の権限を許可してください。")
                .setCancelable(false)
                .setPositiveButton("OK") { dialog, _ ->
                    dialog.dismiss()
                }
                .setNegativeButton("キャンセル") { dialog, _ ->
                    dialog.cancel()
                }
                .create().show()
        }
    }

```

位置情報の権限が許可されたらトーストを表示し、許可されなかったら設定アプリから権限を許可するように促すダイアログを表示します。

リクエストするときは ```launch``` メソッドを使い、引数にリクエストする権限を指定します。

``` MainActivity.kt
button1.setOnClickListener {
  permissionResult.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}

```

## 権限をリクエストする(複数)

権限を複数リクエストすることも可能です。
メソッドは権限が一つの時と同じように ```registerForActivityResult``` メソッドを使うのですが、引数が変わります。
第一引数には ```ActivityResultContracts.RequestPermissions``` を指定します。
第二引数にはコールバック関数を指定するのですが、引数の型は ```Map<String, Boolean>``` となります。
String型の変数は権限名、Boolean型の変数はその権限が許可されたかどうかの結果を表します。

以下はカメラと位置情報の権限をリクエストする例です。

``` MainActivity.kt
    private val permissionResults = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result: Map<String, Boolean> ->
        Log.d("permission result", it.toString())

        it.forEach { (permission, isGrant) ->
            val perm = when (permission) {
                Manifest.permission.ACCESS_FINE_LOCATION -> "位置情報の権限"
                Manifest.permission.CAMERA -> "カメラの権限"
                else -> ""
            }
            if (isGrant) {
                Toast.makeText(this, "${perm}が許可されました。", Toast.LENGTH_SHORT).show()
            } else {
                  AlertDialog.Builder(this)
                      .setMessage("この機能を使用するために${perm}を許可してください。")
                      .setCancelable(false)
                      .setPositiveButton("OK") { dialog, _ ->
                          dialog.dismiss()
                      }
                      .setNegativeButton("キャンセル") { dialog, _ ->
                          dialog.cancel()
                      }
                      .create().show()
            }

        }
    }

```

コールバック関数の Map型の引数から ```forEach``` メソッドによって権限名とリクエスト結果のペアを取り出して権限ごとに日本語のメッセージを生成してダイアログやトーストに表示するようにしています。

権限をリクエストするときは ```launch``` メソッドの引数に配列形式で権限名を指定します。

``` MainActivity.kt
button2.setOnClickListener {
  permissionResults.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CAMERA))
}

```


## 参考
[[Android] Activity Result API を使う](https://zenn.dev/t2low/articles/ea610398e29154e1a887)
[アプリの権限をリクエストする  \|  Android デベロッパー  |  Android Developers](https://developer.android.com/training/permissions/requesting?hl=ja)
[ActivityResultContractsの仕組みを調べてみる - Qiita](https://qiita.com/ryo_mm2d/items/32899f0a1e8c676b470c)