Dexter is an Android library that simplifies the process of requesting permissions at runtime.
Android Marshmallow includes a new functionality to let users grant or deny permissions when running an app instead of granting them all when installing it. This approach gives the user more control over applications but requires developers to add lots of code to support it.
The official API is heavily coupled with the Activity class. Dexter frees your permission code from your activities and lets you write that logic anywhere you want.
Screenshots
Demo screenshot
Usage
Dependency
Include the library in your build.gradle
dependencies{
implementation 'com.karumi:dexter:5.0.0'
}
To start using the library you just need to call Dexter with a valid Activity:
public MyActivity extends Activity {
@Override public void onCreate() {
super.onCreate();
Dexter.withActivity(activity)
.withPermission(permission)
.withListener(listener)
.check();
}
}
Single permission
For each permission, register a PermissionListener implementation to receive the state of the request:
Dexter.withActivity(this)
.withPermission(Manifest.permission.CAMERA)
.withListener(new PermissionListener() {
@Override public void onPermissionGranted(PermissionGrantedResponse response) {/* ... */}
@Override public void onPermissionDenied(PermissionDeniedResponse response) {/* ... */}
@Override public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {/* ... */}
}).check();
To make your life easier we offer some PermissionListener implementations to perform recurrent actions:
BasePermissionListener to make it easier to implement only the methods you want. Keep in mind that you should not call super methods when overriding them.
DialogOnDeniedPermissionListener to show a configurable dialog whenever the user rejects a permission request:
PermissionListener dialogPermissionListener =
DialogOnDeniedPermissionListener.Builder
.withContext(context)
.withTitle("Camera permission")
.withMessage("Camera permission is needed to take pictures of your cat")
.withButtonText(android.R.string.ok)
.withIcon(R.mipmap.my_icon)
.build();
SnackbarOnDeniedPermissionListener to show a snackbar message whenever the user rejects a permission request:
PermissionListener snackbarPermissionListener =
SnackbarOnDeniedPermissionListener.Builder
.with(view, "Camera access is needed to take pictures of your dog")
.withOpenSettingsButton("Settings")
.withCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
// Event handler for when the given Snackbar is visible
}
@Override
public void onDismissed(Snackbar snackbar, int event) {
// Event handler for when the given Snackbar has been dismissed
}
}).build();
CompositePermissionListener to compound multiple listeners into one:
PermissionListener snackbarPermissionListener = /*...*/;
PermissionListener dialogPermissionListener = /*...*/;
PermissionListener compositePermissionListener = new CompositePermissionListener(snackbarPermissionListener, dialogPermissionListener, /*...*/);
Multiple permissions
If you want to request multiple permissions you just need to call withPermissions and register an implementation of MultiplePermissionsListener:
Dexter.withActivity(this)
.withPermissions(
Manifest.permission.CAMERA,
Manifest.permission.READ_CONTACTS,
Manifest.permission.RECORD_AUDIO
).withListener(new MultiplePermissionsListener() {
@Override public void onPermissionsChecked(MultiplePermissionsReport report) {/* ... */}
@Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {/* ... */}
}).check();
The MultiplePermissionsReport contains all the details of the permission request like the list of denied/granted permissions or utility methods like areAllPermissionsGranted and isAnyPermissionPermanentlyDenied.
As with the single permission listener, there are also some useful implementations for recurring patterns:
BaseMultiplePermissionsListener to make it easier to implement only the methods you want. Keep in mind that you should not call super methods when overriding them.
DialogOnAnyDeniedMultiplePermissionsListener to show a configurable dialog whenever the user rejects at least one permission:
MultiplePermissionsListener dialogMultiplePermissionsListener =
DialogOnAnyDeniedMultiplePermissionsListener.Builder
.withContext(context)
.withTitle("Camera & audio permission")
.withMessage("Both camera and audio permission are needed to take pictures of your cat")
.withButtonText(android.R.string.ok)
.withIcon(R.mipmap.my_icon)
.build();
SnackbarOnAnyDeniedMultiplePermissionsListener to show a snackbar message whenever the user rejects any of the requested permissions:
MultiplePermissionsListener snackbarMultiplePermissionsListener =
SnackbarOnAnyDeniedMultiplePermissionsListener.Builder
.with(view, "Camera and audio access is needed to take pictures of your dog")
.withOpenSettingsButton("Settings")
.withCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
// Event handler for when the given Snackbar has been dismissed
}
@Override
public void onDismissed(Snackbar snackbar, int event) {
// Event handler for when the given Snackbar is visible
}
})
.build();
CompositePermissionListener to compound multiple listeners into one:
MultiplePermissionsListener snackbarMultiplePermissionsListener = /*...*/;
MultiplePermissionsListener dialogMultiplePermissionsListener = /*...*/;
MultiplePermissionsListener compositePermissionsListener = new CompositeMultiplePermissionsListener(snackbarMultiplePermissionsListener, dialogMultiplePermissionsListener, /*...*/);
Handling listener threads
If you want to receive permission listener callbacks on the same thread that fired the permission request, you just need to call onSameThread before checking for permissions:
Dexter.withActivity(activity)
.withPermission(permission)
.withListener(listener)
.onSameThread()
.check();
Showing a rationale
Android will notify you when you are requesting a permission that needs an additional explanation for its usage, either because it is considered dangerous, or because the user has already declined that permission once.
Dexter will call the method onPermissionRationaleShouldBeShown implemented in your listener with a PermissionToken. It's important to keep in mind that the request process will pause until the token is used, therefore, you won't be able to call Dexter again or request any other permissions if the token has not been used.
The most simple implementation of your onPermissionRationaleShouldBeShown method could be:
@Override public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
token.continuePermissionRequest();
}
Error handling
If you think there is an error in your Dexter integration, just register a PermissionRequestErrorListener when calling Dexter:
Dexter.withActivity(activity)
.withPermission(permission)
.withListener(listener)
.withErrorListener(new PermissionRequestErrorListener() {
@Override public void onError(DexterError error) {
Log.e("Dexter", "There was an error: " + error.toString());
}
}).check();
The library will notify you when something bad happens. In general, it is a good practice to, at least, log every error Dexter may throw but is up to you, the developer, to do that.
IMPORTANT: Remember to follow the Google design guidelines to make your application as user-friendly as possible.
Caveats
For permissions that did not exist before API Level 16, you should check the OS version and use ContextCompat.checkSelfPermission. See You Cannot Hold Non-Existent Permissions.
Don't call Dexter from an Activity with the flag noHistory enabled. When a permission is requested, Dexter creates its own Activity internally and pushes it into the stack causing the original Activity to be dismissed.
Permissions SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are considered special by Android. Dexter doesn't handle those and you'll need to request them in the old fashioned way.