Android
RxJava
RxJavaDay 1

RxPermissions で Runtime Permission の処理をする

概要

この記事は RxJava Advent Calendar 2017 の初日です。

初日ということで軽めに、RxJava と組み合わせて使うライブラリの1つである RxPermissions
について説明します。

RxPermissions

雑に言うと Runtime Permission の処理を Rx で書くことができるライブラリです。Java で実装されています。master は RxJava 1系向けの実装ですが、 RxJava 2系対応のブランチも用意されています。

導入するモチベーション

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

ライセンス

Apache License 2.0です。配布するアプリで利用する場合、アプリ内で著作権表示をする必要があります。


導入

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

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

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


利用

おことわり

  1. サンプルコードはライブラリ側を Java、利用側を Kotlin-1.1.51 で記載しました。
  2. 今回の記事のサンプルコードでは Disposable の処理を省略いたしました。実際のコードでは CompositeDisposableRxLifecycleAutoDispose 等を用いて Disposable をライフサイクルに合わせて適切に処理する必要があります。その辺のお話は今後の記事で語られそうな予感がしています。

インスタンスの取得

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

private lateinit var rxPermissions: RxPermissions

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

RxPermissions.isGranted(String permission)

権限が許可されているかを調べるメソッドです。

rxPermissions.isGranted(Manifest.permission.READ_EXTERNAL_STORAGE)

ライブラリの実装

実装は下記のようになっていました。

RxPermissions.isGranted(String permission)
public boolean isGranted(String permission) {
    return !isMarshmallow() || mRxPermissionsFragment.isGranted(permission);
}
RxPermissions.isMarshmallow()
boolean isMarshmallow() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
RxPermissionsFragment.isGranted(String permission)
@TargetApi(Build.VERSION_CODES.M)
boolean isGranted(String permission) {
    return getActivity().checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}

RxPermissions を使わない場合

いつもだとこのように怠けて、長い条件を書いてしまうかもしれませんが

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

RxPermissions を使う場合

RxPermissions.isGranted なら大分すっきりします。

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

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

RxPermissions.request(String permission)

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

RxPermissions.request(String 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 で受け取って処理を分岐させる必要があります。

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

権限をリクエスト
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
        && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
    requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
    return
}
リクエスト結果を受けて処理をする
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箇所で書くことができます。

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

ezgif-4-10e56e01aa.gif


注意

利用する権限は AndroidManifest.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 を用いているプロジェクトでは試してみると良いでしょう。

参考