Android Permissions: A New Path to Glory
For more than 4 years, Android Permissions have devoured our time in development to provide our beloved users more transparent, and safer apps for Android platform.
This article is not a complaint but a new idea about the adventure of Android permissions. I don’t claim that the approach will cover all Android permission resolutions. The idea may probably be practiced by the most of you, but I am talking about creating a norm about it. If we can achieve this, the platform will overcome a considerable obstacle which is gathering storms in our brains.
As we all know, Android permissions requires lots of boilerplate code including massive AlertDialog
s. I never liked them. They make the code too large and hard to read for me for years.
Some Android developers created useful libraries to reduce the number of lines in code. I used them too, but I am not really satisfied with their approach. I believe Android permissions are strongly related to business concerns, but those libraries focuses on just refactoring the code. Their approach is not easily manageable, and reusable. Moreover they don’t force us to provide a stable UX for our users. A typical Android user expects the same behavior for native platform events in every apps, as the Android platform itself expects us to maintain it too!
I don’t believe this can be handled by a third party library, but I believe that it can be resolved by a pattern. Android native permission process seems very low level for me according to our innocent activities and fragments. That should get high as much as possible so our activities and fragments can breathe fresh air.
This article will explain how we approached to permissions in Android app of arabam.com and show you our real code to provide a real experience that we faced. I will also provide a sample project on Github.
Let’s start from the highest point of our pattern: BusinessRequest
If you need an Android permission, you absolutely need to solve a business logic in your app, and the permission is probably sitting in the middle of it. That’s why BusinessRequest
answers a base question: What are you going to do with these permissions?
Our activities and fragments will tell what they want to do using it. We also need to define the real permissions remaining in the settings menu:
We will map BusinessRequest
to PermissionRequest
later. These classes will help us better understand what permissions our business needs. More readable!
We need to identify our activities and fragments easily which are using permissions, so they will become children of abstract PermissionActivity
, PermissionFragment
and PermissionDialogFragment
.
As you see we have 3 different abstract classes which they will do almost the same work. How can we manage them to do the same work without copying and pasting the code? The answer is, we need a manager class that will work as a field inside them and handle all business logic in one place and provide the same behavior in all permission requests in the entire app.
Our manager class will be PermissionManager
. It will check whether permissions are granted, and show necessary dialogs like rationale & navigating to settings menu in case of user’s permanent denial. It will handle user choices and will let our 3 abstract classes know what happened in the end.
Wait a second, how can they know what happened in our mysterious PermissionManager
class? We need a messenger between PermissionManager
and 3 abstract classes. As you guess, we need a listener that will invoke essential events performed by PermissionManager
. We call it PermissionListener
.
Our architecture is ready, let’s see how it works to see the big picture:
Let’s start our adventure with an example for an Android permission process: When user wants to make a video call with an advertiser, s/he needs to give following permissions, Camera, Microphone & Phone. We have VideoCallActivity
. It will extend PermissionActivity
.
We will start calling checkPermissionsAreGranted(BusinessRequest.VIDEO_CALL, true)
method implemented in PermissionActivity
. It simply wants to make a video call, doesn’t worry about what permissions are needed. Lovely. Let’s see what happens in the method:
We pass optional parameter exitOnDenied
true because the permissions are critical for VideoCallActivity
. If user denies any of the permissions, we can’t do anything, so we will finish the activity in that case when exitOnDenied
is true.
We put ourselves in PermissionManager
‘s arms. It will call getPermissionRequestsFromBusiness(businessRequest)
mapping BusinessRequest.VIDEO_CALL
to an array of PermissionRequest.CAMERA
, PermissionRequest.MICROPHONE
and PermissionRequest.PHONE
. Then, it will check whether all these permissions are granted by calling areAllPermissionsGranted(this, permissionRequests)
:
If all permissions are granted, it will simply call abstract function onPermissionsGranted(businessRequest)
and VideoCallActivity
will call makeVideoCall()
function inside of it. However, if any of permissions are not granted yet, we will start preparation for permission process by calling prepareNativePermissionRequest(permissionsRequests)
.
It will prepare an Android’s native permission request; an array of strings including Manifest.permission
s, and a requestCode
integer value to match the result:
When they are ready, the function will let PermissionActivity
know that the request is ready by invoking onNativePermissionRequestReady(permissions, requestCode)
method. Then, PermissionActivity
will simply call ActivityCompat.requestPermissions(this, permissions, requestCode)
inside of it.
The result will fall into onRequestPermissionResult
method inside PermissionActivity
. Our superhero, PermissionManager
comes along to aid us again! We call its handleRequestPermissionsResult(...)
method to handle all the result logic for us:
It will check whether all permissions are granted. If user grants all permissions, it will let PermissionActivity
know by invoking grantPermissions(businessRequest)
method. If user denies any of permissions, it will check whether any of denied permissions should show a rationale dialog. If so, we will call showPermissionRationaleDialog(...)
, otherwise we must call showPermissionPermanentDenialDialog(...)
because user denies all permissions permanently.
Rationale dialog will ask user to try again. If user taps on the positive button, it will invoke retry()
method and all process will start over from checkPermissionsAreGranted(...)
method. If user taps on the negative button, it will invoke deny()
method and the process will be terminated.
Permanent Denial dialog will ask user to navigate to the settings menu of the application and grant necessary permissions for further process. If user taps on the positive button, it will invoke navigateToSettings()
method and PermissionActivity
will navigate to settings making isComingFromSettings
as true to handle the result on onResume()
method. (I once tried to handle it on onActivityResult
but it was never called). If user taps on the negative button, it will invoke deny()
method and the process will be terminated.
We handle the result by the following code in PermissionActivity
:
PermissionManager
helps us again with handleResume(this)
to handle the result whether user grants permissions in the settings menu:
It will ask user to try again if s/he grants permissions in the settings menu. If user taps on the positive button, it will invoke retry()
method and all process will start over from checkPermissionsAreGranted(...)
method. If user taps on the negative button, it will invoke deny()
method and the process will be terminated.
As you see, our VideoCallActivity
did almost nothing about about the permission. Everything is handled by the help of PermissionActivity
and PermissionManager
.
Once upon a time, VideoCallActivity
was quite fat and sad with permission works:
It had 136 lines of code, most of them work for permission issue. Our activities just don’t deserve this. Finally, it met with our savior PermissionManager
and become a PermissionActivity
:
Oh my goodness! We have now 22 lines of code. We saved a total number of 114 lines! Now VideoCallActivity
happily lives ever after. This is just an example, imagine what happens when you apply this pattern in your entire project? I am curiously expecting your results!
I hope this article will open a new path to glory for approaching to Android permissions, making a platform-friendly applications to hold the users by our side.
I put a link to the sample repository for having the pattern in your projects. I certainly need your help and feedback to make the pattern much better, and cover more issues out of my sight.