This article will demonstrate how to delegate permission handling to a designated class whose instance in retrieved using dependency retrieval with Kodein.

I will show how to implement and then call a fuction that takes in a list of permissions to request. Each such permission will be accompanied with one action that will be called when the permission has been granted, and one that will be called whet it has been denied.

Configuration of dependency retrieval with Kodein has been already described in this blog in the article called ‘Testing with dependency retrieval’.

Presented herein permission-handling code has been used in the project Wiktor-Navigator.

Structure of the request

The class containing the request contains the name of the request, the action called when it has been granted, the action called when it has beed denied, and doesnt’t contain any fuction. By default both of these actions are empty:

class PermissionRequest(
            val permission: String,
            val onGranted: () -> Unit = {},
            val onDenied: () -> Unit = {})

Requesting the permissions

Permissions are requested by calling a global extention function called on the Fragment. Because I use Navigation Architecture component almost all of GUI is implementet in Fragments as opposed to Activitys, but one could write a similar extention function for Activity as well:

fun Fragment.requestPermissions(
        requestCode: Int,
        requests: Array<Permissions.PermissionRequest>) =
    permissions.request(
            this,
            requestCode,
            requests)

The call permissions automatically retrieves an instance of the permission-handling class, whose Kodein binding is created in the module in this line:

bind<Permissions>() with singleton { Permissions() }

It is then retrieved by this global property:

val permissions get() = instance<Permissions>()

This is the code that actually requests the permissions and subscribes the responses using RxJava:

class Permissions {

    private val grantedSubject = PublishSubject.create<PermissionResult>()
    private val deniedSubject = PublishSubject.create<PermissionResult>()

    fun request(
            fragment: Fragment,
            requestCode: Int,
            requests: Array<PermissionRequest>) {
        val lifecycle = fragment.lifecycle
        val permissionsToRequest = ArrayList<String>()
        requests.forEach { request ->
            permissionsToRequest.add(request.permission)
                grantedSubject
                        .filter { it.requestCode == requestCode && it.permission == request.permission }
                        .subscribe(lifecycle) { request.onGranted() }
                deniedSubject
                        .filter { it.requestCode == requestCode && it.permission == request.permission }
                        .subscribe(lifecycle) { request.onDenied() }
        }
        fragment.requestPermissions(permissionsToRequest.toTypedArray(), requestCode)
    }
    ...
}

It uses lifecycle to automatically dispose the subscriptions when activity ends. Making lifecycle-aware subscribtions has been described in this blog in the article called ‘Lifecycle-aware Rx subscriptions’.

CAUTION: Do not use the method ActivityCompat.requestPermissions(). Although you can adapt the above code to successfully use this method to request permissions, if you do this instead of calling the method requestPermissions directly on Fragment, only the callback onRequestPermissionsResult() inside the Activity (not Fragment) will be called.

In the Fragment

This is the code in the Fragment that requests Location permission, passing to the request the actions that will be called when it is granted or denied:

private fun requestLocationPermission() {
        fun showLocationPermissionSnackbar() {
            snackbar = Snackbar.make(
                    coordinator,
                    R.string.snackbar_permission_request_denied,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.snackbar_retry)  { requestLocationPermission() }
                    .apply { show() }
        }

        requestPermissions(
                Permissions.LOCATION_PERMISSION_REQUEST_ID,
                arrayOf(Permissions.PermissionRequest(
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        onGranted = ::onLocationPermissionGranted,
                        onDenied = ::showLocationPermissionSnackbar)))
}

If the permission is denied, a snackbar will be shown, prompting the user again to grant the permission. When the user chooses to automatically deny the permission each time, this snackbar will just continue to show up, suggesting to the user that this permission is mandatory for the application to function.

Handling responses

Handling the response begins in the Fragment:

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

It retrieves an instance of the permissions-handling class and passes the results to it. The actual permission-handling code:

fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    infix fun String.onGranted(requestCode: Int) =
            grantedSubject.onNext(PermissionResult(requestCode, this))

    infix fun String.onDenied(requestCode: Int) =
            deniedSubject.onNext(PermissionResult(requestCode, this))

    if (grantResults.isEmpty()) {
        permissions.forEach { it onDenied requestCode }
    } else repeat(permissions.size) {
        if (grantResults[it] == PackageManager.PERMISSION_GRANTED) {
            permissions[it] onGranted requestCode
        } else {
            permissions[it] onDenied requestCode
        }
    }
}

It just calls onNext on the appropriate RxJava’s Subjects, and the previously described code handles the subscriptions, disposing them if they are already irrelevant due to the Activity’s finished lifeficle.