Android - Runtime permissionリクエスト方法と例(kotlin)

By JS | Last updated: May 31, 2020

Runtime permissionは、アプリがユーザーに要求をして、ユーザーが許可しなければなら受けることができパーミッションを指します。

そのため、どのようなパーミッションが必要なAPIを使用するには、ユーザーに必ず要求をする必要があります。ユーザーが要求を拒否することもできますので、これに対する例外処理も必要です。

Runtime permissionはSDK API 23(Mashmello)からサポートされます。 SDK API 23未満のバージョンでコンパイルした場合、アプリがインストールされたときのパーミッションを取得することができます。したがってAPI 23未満はRuntime permissionを要求する必要がありません。

アプリのインストール時に得るパーミッションはInstall permission呼ばれます。

パーミッションリクエストAPI

ActivityがAppCompatActivityを継承すると、次のようなAPIを使用することができます。

  • checkSelfPermission()
  • requestPermissions()
  • shouldShowRequestPermissionRationale()

このAPIを使用して、ユーザーにパーミッションを要求することができます。

また、ActivityCompatに同じ動作を実行するAPIを使用することができます。

  • ActivityCompat.checkSelfPermission()
  • ContextCompat.checkSelfPermission()
  • ActivityCompat.requestPermissions()
  • ActivityCompat.shouldShowRequestPermissionRationale()

今、上記のAPIを使用してRuntime permissionをユーザーに要求して取得する方法を説明します。

プロジェクトの設定

この記事のコードは、Kotlinに作成されました。

プロジェクトのTarget SDK APIは、23以降を使用するようにします。

完成したプロジェクトは、GitHub - RequestRuntimePermissionで確認することができます。

パーミッションを持っていることを確認

checkSelfPermission()でアプリがパーミッションを持っていることを確認することができます。

次のように確認したいパーミッションを引数として渡すと、 PERMISSION_GRANTEDまたはPERMISSION_DENIEDが返されます。

if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
    // Permission is granted
} else {
    // Permission is not granted
}

PERMISSION_GRANTEDが返された場合パーミッションを持っているということで、そうでない場合パーミッションがないことを意味します。

パーミッションを持っている場合はどのような機能が実行されるように実装することができます。

ユーザーにパーミッションリクエスト

requestPermissions()でユーザーにPermissionを要求することができます。

次のように、まず checkSelfPermission()でPermissionを持っていることを確認していない場合は requestPermissions()に要求します。

companion object {
    const val PERMISSION_REQUEST_CODE = 1001
}

if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
    showDialog("Permission granted")
} else {
    requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), // 1
            PERMISSION_REQUEST_CODE) // 2
}

//1のように、上記のコメントに表示したものを、以下で詳しく説明しています。

  1. パーミッションを配列として渡される理由は、このAPIは、いくつかのパーミッションを同時に求めることができるように作られたからです。もし一つのパーミッションのみを要求しても、配列に渡す必要があります。
  2. PERMISSION_REQUEST_CODEは単なる定数であるが、アクセス許可の要求に応答してイベントが送出されるときに、このcodeに渡されます。数字は何でもしてもされるが、他のイベントと区別されます。

パーミッション要求の結果を受け取る

requestPermissions()で、ユーザーにパーミッションを要求すると、次のようなポップアップが表示されます。

Android runtime permission popup

ユーザーがallowをクリックするか、denyを押すと、その結果は、私のアプリに転送されます。

Frameworkは、結果を渡すときActivityの onRequestPermissionsResult()を呼び出します。

次は、イベントを受信したとき処理するコードです。

override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {

    when (requestCode) {
        PERMISSION_REQUEST_CODE -> {  // 1
            if (grantResults.isEmpty()) {  // 2
                throw RuntimeException("Empty permission result")
            }
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // 3
                showDialog("Permission granted")
            } else {
                if (shouldShowRequestPermissionRationale(   
                          Manifest.permission.ACCESS_FINE_LOCATION)) { // 4
                    Log.d(TAG, "User declined, but i can still ask for more")
                    requestPermissions(
                            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                            PERMISSION_REQUEST_CODE)
                } else {
                    Log.d(TAG, "User declined and i can't ask")
                    showDialogToGetPermission()   // 5
                }
            }
        }
    }
}

private fun showDialogToGetPermission() {
    val builder = AlertDialog.Builder(this)
    builder.setTitle("Permisisons request")
            .setMessage("We need the location permission for some reason. " +
                    "You need to move on Settings to grant some permissions")

    builder.setPositiveButton("OK") { dialogInterface, i ->
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                Uri.fromParts("package", packageName, null))
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        startActivity(intent)   // 6
    }
    builder.setNegativeButton("Later") { dialogInterface, i ->
        // ignore
    }
    val dialog = builder.create()
    dialog.show()
}

//1のように、上記のコメントに表示したものを、以下で詳しく説明しています。

  1. 要求に使用したcodeでイベントがActivityに転送されます。
  2. grantResultsはemptyになることができません。 emptyなら無視します。
  3. 複数Permissionを要求することができますので、その結果も配列として渡されます。ここでは1つのパーミッションを要求したため、Index 0の結果を確認するだけです。パーミッションを受け取った場合はどのような機能が実行されるように実装することができます。ここポップアップを離すようにしました。
  4. shouldShowRequestPermissionRationale()は、ユーザーが__Denyボタンを押したときにtrueを返します。ユーザーがDeny&Do not ask again__ボタンを押すと、falseを返します。 trueが返される場合、ユーザーに再アクセス許可を要求することができます。
  5. しかし、falseが返される場合 requestPermissionsを呼び出しても、要求のポップアップが出ません。パーミッションを受けるユーザはSettingsの内アプリ情報に入ってパーミッションを変更する必要があります。したがって、なぜパーミッションが必要十分説明するポップアップを浮かべ__OK__ボタンを押すと、Settingsに移動できるように実装しました。
  6. ACTION_APPLICATION_DETAILS_SETTINGSインテントを実行すると、私のアプリの設定画面に移動します。

例の実行結果

requestPermissions()で、ユーザーにパーミッションを要求すると、次のようなポップアップが表示されます。 Android runtime permission popup

__Allow__を押すと、次のようなポップアップが表示されます。 Android runtime permission popup

もし__Deny__を押すと、要求のポップアップが繰り返し表示されます。

__Deny&Do not ask again__を押すと、次のようにアプリがパーミッションを必ず得なければならない理由を説明するポップアップが表示されます。 Android runtime permission popup

__OK__を押すと、マイアプリの設定画面に移動します。 Android runtime permission popup

ここで、ユーザは自分のアプリに許可を付与することができます。 Android runtime permission popup

Fragmentでパーミッション要求

Fragmentでパーミッションを要求すること、Helper classでパーミッションを要求するように実装することができます。

Fragmentもほとんど同じAPIを呼び出すことができますが、呼び出すことができない場合に ActivityCompatを利用して呼び出します。 このAPIを呼び出すときに引数としてactivityを渡しヘジュオヤます。

以下は、Fragmentでパーミッションを要求するコードです。

private fun requestRuntimePermissions() {
    if (ActivityCompat.checkSelfPermission(requireActivity(), Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {
        // Permission granted
    } else {
        ActivityCompat.requestPermissions(
            requireActivity(),
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            MainActivity.PERMISSION_REQUEST_CODE
        )
    }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
                                        grantResults: IntArray) {
    when (requestCode) {
        MainActivity.PERMISSION_REQUEST_CODE -> {
            if (grantResults.isEmpty()) {
                throw RuntimeException("Empty permission result")
            }
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission granted
            } else {
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        requireActivity(),
                        Manifest.permission.ACCESS_FINE_LOCATION)) {
                    Log.d(MainActivity.TAG, "User declined, but i can still ask for more")
                    ActivityCompat.requestPermissions(
                        requireActivity(),
                        arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                        MainActivity.PERMISSION_REQUEST_CODE
                    )
                } else {
                    Log.d(MainActivity.TAG, "User declined and i can't ask")
                }
            }
        }
    }
}

もしパーミッションを要求するHelperクラスを作るなら、 onRequestPermissionsResult()は呼び出されないので、ActivityやFragmentからcallbackされるように実装する必要があります。

プロジェクト全体のコード

プロジェクトのすべてのコードは、GitHub - RequestRuntimePermissionで確認することができます。

参考

Related Posts

codechachaCopyright ©2019 codechacha