Android Runtime Permissions with Dexter

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 just to get a single permission.

In this article, we will talk about how to add runtime permissions easily and quickly using Dexter library. Dexter is an Android library that simplifies the process of requesting permissions at runtime.

This is an introductory article about the Dexter covering basic features offered by the library. Dexter provides other features like using it with SnackBar, different types of listeners, error handling and a few others. You can find more information on Dexter’s developer page.

Get GITHUB code from HERE.

Adding Dexter Library

To get started with Dexter library firstly you have to include it in your build.gradle

dependencies{
    // Dexter runtime permissions
    implementation 'com.karumi:dexter:6.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();
    }
}

Requesting Single Permission

To request single permission, you can use withPermission() method by passing the required permission. You also need a PermissionListener callback to receive the state of the permission.

  •  onPermissionGranted() will be called once the permission is granted.
  •  onPermissionDenied() will be called when the permission is denied. Here you can check whether the permission is permanently denied by using response.isPermanentlyDenied() condition.
  • onPermissionRationaleShouldBeShown() will be called if the user has already declined the permission once. This method contains PermissionToken parameter using which you can call any of the two methods :

continuePermissionRequest() : Continues with the permission request process.

cancelPermissionRequest() : Cancels the permission request process.

The below code requests CAMERA permission.

Dexter.withActivity(this)
    .withPermission(Manifest.permission.CAMERA)
    .withListener(new PermissionListener() {
        
            @Override 
            public void onPermissionGranted(PermissionGrantedResponse response) {
           //permission is granted, open the camera
            }
        @Override 
            public void onPermissionDenied(PermissionDeniedResponse response) {
            // check for permanent denial of permission
           if (response.isPermanentlyDenied()) {
           // navigate user to app settings
}
           }  @Override  public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) { token.continuePermissionRequest(); } })  .check();

Requesting Multiple Permissions

To request multiple permissions at the same time, you can use withPermissions() method. Below code requests STORAGE and LOCATION permissions.

  • onPermissionsChecked() will be called if all permissions are granted.
  • onPermissionRationaleShouldBeShown() will be called if the user has already declined the permission once.
Dexter.withActivity(this)
  .withPermissions(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_FINE_LOCATION)
  .withListener(new MultiplePermissionsListener() {
     @Override
     public void onPermissionsChecked(MultiplePermissionsReport report) {
        // check if all permissions are granted
        if (report.areAllPermissionsGranted()) {
            // do you work now
        }
        // check for permanent denial of any permission
        if (report.isAnyPermissionPermanentlyDenied()) {
        // permission is denied permenantly, navigate user to app settings
           
        }
    }

     @Override
     public void onPermissionRationaleShouldBeShown(List<Permissionsrequest> permissions, PermissionToken token) {
        token.continuePermissionRequest();
    }
})
 .check();

Error Handling

You can also catch any errors occurred while integrating the library using PermissionRequestErrorListener.

 Dexter.withActivity(this)
   .withPermissions(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_FINE_LOCATION)
   .withListener(listener) 
   .withErrorListener(new PermissionRequestErrorListener() {
     @Override
     public void onError(DexterError error) {
        Toast.makeText(getApplicationContext(), "Error occurred! "+ error.toString() , Toast.LENGTH_SHORT).show();
    }
})
 .check();

Now let’s see how to use Dexter in an example project.

Creating New Project

1. Create a new project in Android Studio from File ⇒ New Project and fill the project details.

2. Add the latest Dexter dependency to your build.gradle.

build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    // Dexter runtime permissions
    implementation 'com.karumi:dexter:6.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

3. Open AndroidManifest file and add all the permissions which you want to request.

AndroidManifest.xml 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.dexterpermissionsdemo">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

4. Open the layout file of your main activity (activity_main.xml ) and add two buttons to test different permission methods.

activity_main.xml 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:orientation="vertical"
        android:paddingLeft="16dp"
        android:paddingRight="16dp">

        <Button
            android:id="@+id/btn_camera_permission"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:text="CAMERA PERMISSION" />

        <Button
            android:id="@+id/btn_multiple_permissions"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="MULTIPLE PERMISSIONS" />

    </LinearLayout>

</RelativeLayout>

5. Open MainActivity.java and do the modification as shown below.

  • requestCameraPermission() requests for camera permission.
  • requestMultiplePermission() requests multiple permissions at once.
  • response.isPermanentlyDenied() and report.isAnyPermissionPermanentlyDenied() checks if the permission is denied permanently. Here we have to navigate user to app settings screen by showing a dialog.

MainActivity.java

package com.example.dexterpermissionsdemo;

import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.DexterError;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.PermissionRequestErrorListener;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import com.karumi.dexter.listener.single.PermissionListener;

import java.util.List;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private Button btnCameraPermission, btnMutiplePermissions;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        btnCameraPermission = findViewById(R.id.btn_camera_permission);
        btnMutiplePermissions = findViewById(R.id.btn_multiple_permissions);

        btnCameraPermission.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestCameraPermission();
            }
        });

        btnMutiplePermissions.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestMutiplePermission();
            }
        });
    }

    /**
     * Requesting multiple permissions (storage and location) at once
     * This uses multiple permission model from dexter
     */
    private void requestMutiplePermission() {
        Dexter.withActivity(this)
                .withPermissions(
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.ACCESS_FINE_LOCATION)
                .withListener(new MultiplePermissionsListener() {
                    @Override
                    public void onPermissionsChecked(MultiplePermissionsReport report) {
                        // check if all permissions are granted
                        if (report.areAllPermissionsGranted()) {
                            Toast.makeText(getApplicationContext(), "All permissions are granted!", Toast.LENGTH_SHORT).show();
                        }

                        // check for permanent denial of any permission
                        if (report.isAnyPermissionPermanentlyDenied()) {
                            // show alert dialog navigating to Settings
                            showSettingsDialog();
                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).
                withErrorListener(new PermissionRequestErrorListener() {
                    @Override
                    public void onError(DexterError error) {
                        Toast.makeText(getApplicationContext(), "Error occurred!"+ error.toString(), Toast.LENGTH_SHORT).show();
                    }
                })
                .onSameThread()
                .check();
    }

    /**
     * Requesting camera permission
     * This uses single permission model from dexter
     * Once the permission granted, opens the camera
     */
    private void requestCameraPermission() {
        Dexter.withActivity(this)
                .withPermission(Manifest.permission.CAMERA)
                .withListener(new PermissionListener() {
                    @Override
                    public void onPermissionGranted(PermissionGrantedResponse response) {
                        // permission is granted
                        openCamera();
                    }

                    @Override
                    public void onPermissionDenied(PermissionDeniedResponse response) {
                        // check for permanent denial of permission
                        if (response.isPermanentlyDenied()) {
                            showSettingsDialog();
                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).check();
    }

    /**
     * Showing Alert Dialog with Settings option
     * Navigates user to app settings
     */
    private void showSettingsDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("Need Permissions");
        builder.setMessage("This app needs permission to use this feature. You can grant them in app settings.");
        builder.setPositiveButton("GOTO SETTINGS", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
                openSettings();
            }
        });
        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });
        builder.show();

    }

    // navigating user to app settings
    private void openSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivityForResult(intent, 101);
    }

    private void openCamera() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        startActivityForResult(intent, 100);
    }
}

When you run your application it will look like this as shown below :

Screenshot_1573117321    Screenshot_1573117433

Leave a Reply