As of API 23 (Marshmallow), the permission model for Android has changed significantly. Now, rather than all being setup at install-time, certain dangerous permissions must be checked and activated at runtime instead.
For a full breakdown of how runtime permissions work, check out the understanding app permissions guide. This guide is focused on the practical approach for managing runtime permissions, requesting access to a feature, and managing error cases where the permission is denied.
The easiest way to manage runtime permissions is by using third-party libraries. In this guide, we will be taking a look at the PermissionsDispatcher library. The library is 100% reflection-free and as such does not cause significant performance penalties.
First, we need to recognize the dangerous permissions that require us to request runtime permissions. This includes but is not limited to the following common permissions:
Name | Description |
---|---|
Manifest.permission.READ_CALENDAR |
Read calendar events |
Manifest.permission.WRITE_CALENDAR |
Write calendar events |
Manifest.permission.CAMERA |
Access camera object |
Manifest.permission.READ_CONTACTS |
Read phone contacts |
Manifest.permission.WRITE_CONTACTS |
Write phone contacts |
Manifest.permission.ACCESS_FINE_LOCATION |
Access precise location |
Manifest.permission.ACCESS_COARSE_LOCATION |
Access general location |
Manifest.permission.RECORD_AUDIO |
Record with microphone |
Manifest.permission.CALL_PHONE |
Call using the dialer |
Manifest.permission.READ_EXTERNAL_STORAGE |
Read external or SD |
Manifest.permission.WRITE_EXTERNAL_STORAGE |
Write to external or SD |
The full list of dangerous permissions contains all permissions that require runtime management. Please note that permissions in the normal permission group do not require run-time checks as outlined here.
Make sure to upgrade to the latest Gradle version.
And on your app module in app/build.gradle
:
dependencies {
implementation 'com.github.hotchemi:permissionsdispatcher:3.1.0'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.1.0'
}
Be sure to use the latest version: available.
Suppose we wanted to record be able to call a number using the phone's dialer. Since this is a dangerous permission, we need to ask the user for the permission at runtime with the Manifest.permission.CALL_PHONE
permission. This requires us to do the following:
@RuntimePermissions
@NeedsPermission
First, we need to annotate the activity or fragment with @RuntimePermissions
:
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
// ...
}
Next, we need to annotate the method that requires the permission with @NeedsPermission
tag:
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
@NeedsPermission(Manifest.permission.CALL_PHONE)
void callPhone() {
// Trigger the calling of a number here
}
}
After compiling the project, we need to delegate the permission events to the generated helper class ([Activity Name] + PermissionsDispatcher):
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
// ...
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// NOTE: delegate the permission handling to generated method
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
}
In the code base, we can trigger the call with the appropriate runtime permission checks using the generated methods suffixed with WithCheck
on the helper class:
// NOTE: delegate the permission handling to generated method
MainActivityPermissionsDispatcher.callPhoneWithPermissionCheck(this);
This will invoke the callPhone
method wrapped with the appropriate permission checks.
We can also optionally configure the rationale dialog, handle the denial of a permission or manage when the user requests never to be asked again:
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
// ...
// Annotate a method which explains why the permission/s is/are needed.
// It passes in a `PermissionRequest` object which can continue or abort the current permission
@OnShowRationale(Manifest.permission.CALL_PHONE)
void showRationaleForPhoneCall(PermissionRequest request) {
new AlertDialog.Builder(this)
.setMessage(R.string.permission_phone_rationale)
.setPositiveButton(R.string.button_allow, (dialog, button) -> request.proceed())
.setNegativeButton(R.string.button_deny, (dialog, button) -> request.cancel())
.show();
}
// Annotate a method which is invoked if the user doesn't grant the permissions
@OnPermissionDenied(Manifest.permission.CALL_PHONE)
void showDeniedForPhoneCall() {
Toast.makeText(this, R.string.permission_call_denied, Toast.LENGTH_SHORT).show();
}
// Annotates a method which is invoked if the user
// chose to have the device "never ask again" about a permission
@OnNeverAskAgain(Manifest.permission.CALL_PHONE)
void showNeverAskForPhoneCall() {
Toast.makeText(this, R.string.permission_call_neverask, Toast.LENGTH_SHORT).show();
}
}
With that we can easily handle all of our runtime permission needs.
In addition the the PermissionsDispatcher outlined above, there are many other popular permissions libraries with various APIs and alternate designs including the following:
A full list of permissions libraries can be found here.