Android permission: Can it be easier?

Mykhailo Kovalyk
3 min readNov 1, 2021

--

Intro

If you ever worked with Runtime Permission in Android, you probably have seen this picture:

It is more or less simple and understandable diagram about permission flow in Android. Moreover, with the androidx extensions to permission management, it is much easier to work with (registerForActivityResult(RequestPermission()). In practice, though, it is not so simple. So let’s dive deep into details

Essentially, there are only 4 possible states for permission:

Granted, Denied, PermanentlyDenied, Confused

All these 4 states should be handled starting from step 4:

when {
ContextCompat.checkSelfPermission(
CONTEXT,
Manifest.permission.REQUESTED_PERMISSION
) == PackageManager.PERMISSION_GRANTED -> {
// Granted
}
shouldShowRequestPermissionRationale(...) -> {
// Confused
showRationale(...)
}
else -> {
// Denied/PermanentlyDenied
request()
}
}

And 3 more after permission has been requested (Granted, Denied, PermanentlyDenied):

val requestPermissionLauncher =
registerForActivityResult(
RequestPermission()
)
{
isGranted: Boolean ->
if (
isGranted) {
// Granted
} else {
if (
shouldShowRequestPermissionRationale(...)){
// Denied
}
else {
// PermanentlyDenied
}
}

Problems

It is still too much code required to properly set up permission flow. Registering request result, verifying granted state, verifying if rationale should be shown, handling permanent denial, etc. All of these steps are quite repetitive and error-prone. Also, some pitfalls of the implementation:

  • After the first denial, all further invocation of shouldShowRequestPermissionRationale() would return true unless a user denies it permanently. In other words, relying solely on that indicator to show Rationale dialog would end up into an infinite loop of rationale dialogs. Additional local storage is required to indicate whether the rationale dialog was shown or not.
  • The only way to identify if permission was denied permanently is a combination of shouldShowRequestPermissionRationale = false and isGranted = false

Solution

What is the better option for that? What about the following:

private val cameraRequester: PermissionRequester =
permissionManager.forPermission(Permissions.Camera)
.onDenied { ->
Log.d("QQQ", "denied")
}
.onPermanentlyDenied {
Log.d("QQQ", "permanently denied")
}
.onGranted {
Log.d("QQQ", "granted")
}
.onRationale {
Log.d("QQQ", "rationale")
}
.subscribe(this)
....
cameraRequester.request()

Looks much better, isn’t it? It is much more intuitive which state the app is at and only one place to handle.

Why PermanentlyDenied state?

When a user presses “Deny& don't ask again” there is no way to show a dialog when the user tries to access the feature again and decided to grant the permission. It will always be denied instead. Anyway, it is always possible to ignore if not needed (don’t pass onPermanentlyDenied callback).

Conclusion

Problems SOLVED:

  • Easier management with permission’s callbacks
  • Platform independent — excellent for testing and integration on a lower level (ViewModels)
  • Proper handling of a Permanent denial, Rationale prompt, so no additional checks, conditions, etc
  • Lifecycle-aware

Problems NOT SOLVED:

  • Declaring and subscribing to permission results at the moment of Fragment instantiation — limitation of the Android system.
  • Multiple permission simultaneously — can be added in future if demanded :)

It was not implemented as a separate library just to make it easier to extend and adjust for personal usage. Feel free to give it a try! Any comments or suggestions are appreciated.

Gist: https://gist.github.com/mkovalyk/1e80a975a7f7fa9a895c7e6915041009

P.S. You still have to include androidx.activity to your project.

--

--