Android Phones

Android Runtime Permissions with Dexter Library

Vidhi Markhedkar
Android Runtime Permissions with Dexter Library

Handling runtime permissions in Android can be a cumbersome task, especially with the introduction of runtime permission requests in Android Marshmallow (API level 23).

The Dexter library simplifies this process by providing an easy-to-use API for managing permissions.

In this tutorial, we’ll walk you through the implementation of Dexter to handle both single and multiple permissions in your Android app.

Why Use the Dexter Library?

In Android Marshmallow, users are prompted to grant permissions at runtime rather than at the time of app installation. While this is a great step forward in terms of user privacy, it requires developers to write additional code to handle permission requests and responses. Dexter streamlines this process, allowing you to request permissions with minimal code, while also handling edge cases like permanent denial of permissions.

Step-by-Step Implementation of Dexter Library

1. Add Dexter to Your Project

First, you need to add the Dexter library to your project. Open your build.gradle (Module: app) file and add the following dependency:

implementation 'com.karumi:dexter:5.0.0'

Sync your project to ensure the library is properly added.

2. Declare Permissions in the Android Manifest

Next, declare the permissions your app needs in the AndroidManifest.xml file. For example:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<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" />
<uses-permission android:name="android.permission.CALL_PHONE" />

This step is crucial as it lets the Android system know about the permissions your app will request at runtime.

3. Create the Layout File

Create an XML layout file that defines the user interface for requesting permissions. Here’s a simple example:

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="DEXTER LIBRARY DEMO"
        android:textAlignment="center"
        android:textSize="20sp"
        android:textColor="#ffffff"
        android:background="#000000" />

    <Button
        android:id="@+id/b1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Single Permission"
        android:layout_marginTop="50dp"
        android:onClick="requestSinglePermission" />

    <Button
        android:id="@+id/b2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Multiple Permissions"
        android:layout_marginTop="50dp"
        android:onClick="requestMultiplePermissions" />
</LinearLayout>

This layout includes two buttons: one for requesting a single permission and another for multiple permissions.

4. Implementing Permission Requests in Java (MainActivity.java)

Now, let’s implement the logic to request permissions using Dexter in your MainActivity.java file.

package com.example.dexterlibrary;

import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
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;

public class MainActivity extends AppCompatActivity {

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

    public void requestSinglePermission(View view) {
        Dexter.withActivity(this)
                .withPermission(Manifest.permission.CAMERA)
                .withListener(new PermissionListener() {
                    @Override
                    public void onPermissionGranted(PermissionGrantedResponse response) {
                        Toast.makeText(MainActivity.this, "Camera permission granted", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onPermissionDenied(PermissionDeniedResponse response) {
                        if (response.isPermanentlyDenied()) {
                            showSettingsDialog();
                        }
                    }

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

    public void requestMultiplePermissions(View view) {
        Dexter.withActivity(this)
                .withPermissions(
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_CONTACTS,
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.CALL_PHONE,
                        Manifest.permission.CAMERA)
                .withListener(new MultiplePermissionsListener() {
                    @Override
                    public void onPermissionsChecked(MultiplePermissionsReport report) {
                        if (report.areAllPermissionsGranted()) {
                            Toast.makeText(MainActivity.this, "All permissions granted!", Toast.LENGTH_SHORT).show();
                        }

                        if (report.isAnyPermissionPermanentlyDenied()) {
                            showSettingsDialog();
                        }
                    }

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

    private void showSettingsDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("Permissions Required")
                .setMessage("This app requires permissions to function properly. You can grant them in the app settings.")
                .setPositiveButton("Go to Settings", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                        openSettings();
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .show();
    }

    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);
    }
}

4.1 Granting Single Permission

The requestSinglePermission() method handles the request for a single permission, such as accessing the camera. If the permission is granted, a toast message is displayed. If denied, it checks if the permission is permanently denied and prompts the user to enable it in the app settings.

4.2 Granting Multiple Permissions

The requestMultiplePermissions() method allows you to request multiple permissions simultaneously. Dexter will handle the logic to check if all permissions are granted or if any are permanently denied. Similar to the single permission method, it provides feedback to the user through toast messages and dialogs.

Why Choose Dexter Over Native Methods?

Dexter offers a more streamlined and less error-prone approach compared to manually handling permissions. It abstracts much of the boilerplate code required when managing permissions natively, making your codebase cleaner and easier to maintain. Additionally, Dexter provides robust handling for scenarios such as permanent denial of permissions, offering a smoother user experience.

Common Issues and Troubleshooting

  • Handling Permanent Denials: If a user permanently denies a permission, it’s crucial to guide them to the app settings to manually enable it. Dexter helps manage this scenario through the showSettingsDialog() method.
  • Error Handling: Dexter includes a listener for errors (PermissionRequestErrorListener), ensuring that your app can gracefully handle unexpected issues during the permission request process.

Conclusion

The Dexter library is an excellent tool for simplifying the management of runtime permissions in Android. By integrating Dexter into your app, you can reduce the amount of code needed to handle permissions and ensure a smoother, more user-friendly experience.

If you’re dealing with runtime permissions in your Android project, give Dexter a try. It’s easy to implement, reduces boilerplate code, and provides a consistent way to handle permissions across different Android versions.