Finally, a clean way to deal with permissions in Android

Fidel Montesino
Kin + Carta Created
4 min readDec 21, 2020

Let’s be honest. As a developer, dealing with permissions in Android is a pain.

But not anymore.

In the old Android days, all permissions were declared in the Manifest and the user would review and accept them at installation time. With the release of Android 6 that changed, and every potentially dangerous permission would have to be requested and accepted in runtime. The user would be able to deny that permission (and use the Deny and don’t ask again option!), or even revoke it once it was accepted. More recently, with the release of Android 11, users can give permissions once or only while the app is running, and the operating system can revoke those permissions if the app hasn’t been used for a long time.

Following the Android best practices, we should:

  • Wait until the user triggers an action that requires runtime permissions.
  • Check if they have already granted them.
  • Check if we should display a rationale to give some context to the user about why we require those permissions.
  • Request the runtime permissions.
  • Check if they accepted or denied them.
  • Make our app react accordingly
Workflow for requesting runtime permissions. Source: Android Developer Documentation

Activity Result APIs to the rescue

Introduced in AndroidX Activity 1.2.0 and Fragment 1.3.0 (both in rc01 at the time of writing this), the Activity Result APIs allow your Activities or Fragments to register and launch for activity result:

val takePicture = registerForActivityResult(TakePicturePreview()) { 
bitmap: Bitmap -> // Handle the image as a Bitmap
}
takePicture.launch()

And that’s it! No need for overridingonActivityResult() or custom request code checks anymore!

There are a lot of very useful predefined ActivityResultContract for you to use directly, or you can create a custom one.

Introducing the PermissionManager

After reading about the ActivityResult APIs, the first thing that came to my mind was the painful permission workflow, and how it reminded me of the old but gold RxPermissions library, but in a more native and Kotlin way.

So I decided to give it a try and I’ve created a small, simple Kotlin class to help us all deal with the permission workflow: the PermissionManager.

Using it is as simple as registering the PermissionManager with your Fragment and, whenever you want to request permissions, you just have to send what permissions you need, a rationale of why you’re requesting those permissions and then simply get the results in a callback:

class YourFragment : Fragment() {

private val permissionManager = PermissionManager.from(this)
yourView.setOnClickListener {
permissionManager
.request(Permission.Camera)
.rationale("We need permission to use the camera")
.checkPermission { granted: Boolean ->
if (granted) {
// Do something with the camera
} else {
// You can't access the camera
}
}
}
}

Easy, right? If a rationale is required, it will display an AlertDialog giving the user more context and after the user dismisses the Dialog, it will try to request for permissions again.

The PermissionManager needs to be instantiated at the same time as your Fragment, or inside onCreate. This is because any Activity or Fragment using the Activity Result APIs need to register for result when they are instantiated.

You’re probably wondering whatPermission.Camera looks like. Well, thePermission class is nothing but a sealed class that contains one or more permissions:

sealed class Permission(vararg val permissions: String) {
// Individual permissions
object Camera : Permission(CAMERA)

// Bundled permissions for MyFeature
object MyFeature : Permission(CAMERA, WRITE_EXTERNAL_STORAGE)
}

Remember to declare your permissions in your Manifest as well!

You can request for multiple permissions at once by bundling them, as shown above, or just by sending them to the PermissionManager. The checkPermission { } will return a Boolean telling you whether all permissions were granted or not. If you want more granular control about which permissions were granted or declined, you can use checkDetailedPermission { } to receive a Map<Permission, Boolean> with the results:

permissionManager
.request(Permission.Camera, Permission.Storage)
.rationale("We need two permissions at once!")
.checkDetailedPermission { result: Map<Permission, Boolean> ->
if (result.all { it.value }) {
// We have all the permissions
} else {
// Check in result which permission was denied
}
}

You can check out the code in this GitHub repository:

Future work and customisation

I didn’t want to provide this PermissionManager as a library, mainly because every project will have many different requirements. For instance, right now it’s just showing a plain, boring AlertDialog when the user needs to see the rationale. What if your designer wants it to be a fancy BottomSheet? You can go and customise the displayRationale() method.

Same goes for every Permission you might need to use. Or if you don't use Fragments and need to change/extend the PermissionManager to also work with Activities.

To sum up, you can use this small class as “plug and play” if you want, but you’re also more than free to customise to your own needs.

Conclusion

Dealing with Activity Results will be hugely improved shortly with the new Activity Result APIs. It will open the door for better, cleaner and more readable code.

We can use these new APIs for our benefit, simplifying the whole permission workflow and reducing the boilerplate in your Activities and Fragments.

How do you feel about this approach in dealing with permissions? Would you use something different? Drop a line in the comments!

--

--