Runtime Permissionsに対応するPermissionsDispatcherをリリースしました

そろそろ秋が近づいてきたのですが、モバイル開発をしている人は新OSの対応やらで忙しい時期ではないでしょうか。

Androidは例年ほぼ何もしなくても良くて気が楽だったのですが、今年は比較的ドラスティックでRuntime Permissionsという機能が追加される予定です。

今までだとGoogle Playからインストールする時に全ての権限をユーザーが確認して許可していたのですが、はっきり言ってかなり怖い&一度インストールしてしまうとユーザーがコントロールできない、といったマイナス面が多く今回ついにGoogleが重い腰を上げたという事の様です。

本件、ユーザー的には良い事尽くめなのですが、開発者的には結構頭が痛かったりします。

頭痛の種は色々とあるのですが、

  • targetSdkVersionをMにしない場合でも、ユーザーが設定画面から権限をrevokeできてしまう
  • ユーザーがいつでも権限をrevokeできてしまうので、初回のみ権限を尋ねれば良いというわけでもない
  • もうpreview2のはずだけどshouldShowRequestPermissionRationaleとかよく分からんメソッドも出てきてなんだか不安…
  • 単純にコードが汚くなる

コードが汚くなる問題はなかなか悩ましく、公式サンプルを眺めてもこれが色んな箇所に散在するのは厳しいものがあるな…という感じです。

余談ですが公式サンプルにはバグがあり、またドキュメントも所々怪しい所があり、得も知れぬ不安をかき立てます。(さもActivityに生えているメソッドで処理しないといけないかの様な書きっぷりだが実際はFragmentでも対応できる、など)。

という事でなるべく効率的に処理できる方法はないかと考えてた結果、PermissionsDispatcherというライブラリをリリースしました。

github.com

使い方はREADMEに書いてあるのですが、ざっくりというとAnnotation Processingを使ってコンパイル時に自動で処理をハンドリングするクラスを作成し、ActivityやFragmentからは生成されたメソッドをコールするだけで良い事にしようね、というものです。

例えば、Sampleコードからは裏で以下の様なコードが生成されます。ユーザーは何も考えずに対応するメソッドを呼んであげればいいというわけです。

package permissions.dispatcher.sample;

import java.lang.String;
import permissions.dispatcher.PermissionUtils;

public final class MainActivityPermissionsDispatcher {
  private static final int REQUEST_SHOWCAMERA = 0;

  private static final int REQUEST_SHOWCONTACTS = 1;

  private MainActivityPermissionsDispatcher() {
  }

  public static void showCameraWithCheck(MainActivity target) {
    if (PermissionUtils.hasSelfPermissions(target, "android.permission.CAMERA")) {
      target.showCamera();
    } else {
      if (target.shouldShowRequestPermissionRationale("android.permission.CAMERA")) {
        target.showRationaleForCamera();
      }
      target.requestPermissions(new String[]{"android.permission.CAMERA"}, REQUEST_SHOWCAMERA);
    }
  }

  public static void showContactsWithCheck(MainActivity target) {
    if (PermissionUtils.hasSelfPermissions(target, "android.permission.READ_CONTACTS")) {
      target.showContacts();
    } else {
      if (target.shouldShowRequestPermissionRationale("android.permission.READ_CONTACTS")) {
        target.showRationaleForContact();
      }
      target.requestPermissions(new String[]{"android.permission.READ_CONTACTS"}, REQUEST_SHOWCONTACTS);
    }
  }

  public static void onRequestPermissionsResult(MainActivity target, int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
      case REQUEST_SHOWCAMERA:
      if (PermissionUtils.verifyPermissions(grantResults)) {
        target.showCamera();
      }
      break;
      case REQUEST_SHOWCONTACTS:
      if (PermissionUtils.verifyPermissions(grantResults)) {
        target.showContacts();
      }
      break;
      default:
      break;
    }
  }
}

一点、生成するコードはPreview2の仕様に基づいて作成されているので、今後ドラスティックに変更を加える可能性がありますが、他人事でもないので可能な限り追随できるといいなと思っています。

仕様の誤り、機能要望などがありましたらissueまで報告頂けますと幸いです。取り急ぎ、複数のpermissionのrequestに未対応なので対応したいなと思っています。

ちなみに、アプリがPermissionに対応する必要があるかどうかは、Android Developersをじっくり読むというのでも勿論良いのですが、拙著のスクリプトを使うと概ね正確に把握できます(カスタムパーミッションは仕様上無理なので検査していません。詳しくはREADMEを)。

github.com

最後に、技術的な事で参考にした資料を以下に並べます。

Permissions

Annotation Processing

現場からは以上です。