RxPermissions で Runtime Permission の処理をする

# 概要
この記事は [RxJava Advent Calendar 2017](https://qiita.com/advent-calendar/2017/rxjava) の初日です。

初日ということで軽めに、RxJava と組み合わせて使うライブラリの1つである [RxPermissions](https://github.com/tbruyelle/RxPermissions/)
について説明します。

## [RxPermissions](https://github.com/tbruyelle/RxPermissions/)
雑に言うと Runtime Permission の処理を Rx で書くことができるライブラリです。Java で実装されています。master は RxJava 1系向けの実装ですが、 [RxJava 2系対応のブランチ](https://github.com/tbruyelle/RxPermissions/tree/2.x)も用意されています。

### 導入するモチベーション
Android の Runtime Permission 関連の処理は長ったらしく、かつ箇所が分散するのでコードを見にくくしがちですが、この RxPermissions を使うと良い具合に書くことができます。

## ライセンス
[Apache License 2.0](https://github.com/tbruyelle/RxPermissions/blob/master/LICENSE)です。配布するアプリで利用する場合、アプリ内で著作権表示をする必要があります。

----

# 導入
今回は2系のを使ってみます。app/build.gradle に下記の依存を追加します。

```groovy:app/build.gradle
dependencies {
    implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    implementation 'io.reactivex.rxjava2:rxjava:2.1.5'
```

修正が終わったら sync してください。

## Referenced Methods
0.9.4 の時点では 55 でした。 RxJava をすでに導入しているプロジェクトであれば、このくらいの増分はクリティカルにはならないでしょう。

![rxpermissions.png](https://qiita-image-store.s3.amazonaws.com/0/102004/2bb2198d-7b11-a1a3-a838-e1a16e03875a.png)

ただ、RxJava を使っていないプロジェクトの場合、これに加えて RxJava の1万近いメソッドが増えますので、 RxPermissions を使いたいからというだけでおいそれと RxJava を入れるわけにはいかないかと思われます。

----
# 利用

## おことわり
1. サンプルコードはライブラリ側を Java、利用側を Kotlin-1.1.51 で記載しました。
2. 今回の記事のサンプルコードでは Disposable の処理を省略いたしました。実際のコードでは [CompositeDisposable](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/disposables/CompositeDisposable.html) や [RxLifecycle](https://github.com/trello/RxLifecycle)、[AutoDispose](https://github.com/uber/AutoDispose) 等を用いて Disposable をライフサイクルに合わせて適切に処理する必要があります。その辺のお話は今後の記事で語られそうな予感がしています。

## インスタンスの取得
Activity を引数にして RxPermissions のインスタンスを取得します。

```kotlin
private lateinit var rxPermissions: RxPermissions

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    rxPermissions = RxPermissions(this)
```

## RxPermissions.isGranted(String permission)
権限が許可されているかを調べるメソッドです。

```kotlin
rxPermissions.isGranted(Manifest.permission.READ_EXTERNAL_STORAGE)
```

### ライブラリの実装
実装は下記のようになっていました。

```java:RxPermissions.isGranted(String&nbsp;permission)
public boolean isGranted(String permission) {
    return !isMarshmallow() || mRxPermissionsFragment.isGranted(permission);
}
```

```java:RxPermissions.isMarshmallow()
boolean isMarshmallow() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
```

```java:RxPermissionsFragment.isGranted(String&nbsp;permission)
@TargetApi(Build.VERSION_CODES.M)
boolean isGranted(String permission) {
    return getActivity().checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
```

### RxPermissions を使わない場合
いつもだとこのように怠けて、長い条件を書いてしまうかもしれませんが

```java
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
    || checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    //...
```

### RxPermissions を使う場合
RxPermissions.isGranted なら大分すっきりします。

```kotlin
if (rxPermissions.isGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) {
```

まあ、このくらいならライブラリを導入するまでもなく適切にメソッドに分ければいい話です。

## RxPermissions.request(String permission)
RxPermissions の利用はこちらがメインです。引数で指定した権限が許可されているかを確認し、実行環境が API レベル 23未満、あるいは許可されている場合は true を、許可されていない場合は権限許可のダイアログを表示し、そこでも許可されない場合に false を通知します。

```kotlin:RxPermissions.request(String&nbsp;permission)
rxPermissions
        .request(Manifest.permission.CAMERA)
        .subscribeOn(Schedulers.io())
        .subscribe(
            { granted ->
                if (granted) {
                    // 権限を使う処理
                } else {
                    // 権限が使えない時の処理(Toast, Snackbar等によるユーザへのフィードバック、ほか)
                }
            },
            { Timber.e(it) }
        )
```

### RxPermissions を使わない場合
通常、Runtime Permission の処理は Activity 内で requestPermissions メソッドを呼び出し、
その結果を Override した onRequestPermissionsResult で受け取って処理を分岐させる必要があります。

例えば、カメラの権限を取得したい場合は下記の通りです。

```kotlin:権限をリクエスト
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
        && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
    requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
    return
}
```

```kotlin:リクエスト結果を受けて処理をする
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // 権限を使った処理
        return
    }
    // 権限を使えなかった時の処理
}
```

やたらと長ったらしい上に null チェック等も煩わしく、何より権限をリクエストしたメソッドから離れた箇所で処理を書かなければいけないので、
工夫しないとコードがひたすら読みにくくなります。こういうところが嫌になる方もいらっしゃるのではないかと思います。
あと、上の例はリクエストした権限と同一かのチェックを書いていませんね。怠けている悪い例です。

### RxPermissions を使うとどうか？
それが、RxPermissions を使うと下記のように、権限のリクエストからその結果を受けての処理までを1箇所で書くことができます。

```kotlin:RxPermissions.request(String&nbsp;permission)
rxPermissions
        .request(Manifest.permission.CAMERA)
        .subscribeOn(Schedulers.io())
        .subscribe(
            { granted ->
                if (granted) {
                    // 権限を使う処理
                } else {
                    // 権限が使えない時の処理(Toast, Snackbar等によるユーザへのフィードバック、ほか)
                }
            },
            { Timber.e(it) }
        )
```

![ezgif-4-10e56e01aa.gif](https://qiita-image-store.s3.amazonaws.com/0/102004/72afb527-a43d-1d10-fa73-7173ffb4bbd6.gif)


----
# 注意
利用する権限は AndroidManifest.xml の中で指定しておく必要があります。当たり前のことでわざわざ書くまでもないと思いますが、検証用のアプリを作る際に引っかかったので一応書いておきます。

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.toastkid.rxpermissionssample">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
```

これを忘れると、権限リクエストのダイアログが表示されません。

----

# まとめ
RxJava と組み合わせて Runtime Permission の処理を書きやすくするライブラリの RxPermissions を紹介致しました。
煩雑な記述が必要になる Runtime Permission の処理を、Rx の文法できれいに書くことができます。 
Referenced Methods もさほど多くはないため、すでに RxJava を用いているプロジェクトでは試してみると良いでしょう。

## 参考
- [使い方を調べる際に作ったアプリのコード(GitHub)](https://github.com/toastkidjp/RxPermissionsExample/releases/tag/qiita)