Starting with Android 6.0 (Marshmallow, API level 23), Android introduced runtime permission requests for dangerous permissions. Instead of granting every permission during installation, the operating system allows applications to request certain permissions when the associated feature is needed.
In this tutorial, we will learn how to handle runtime permissions using the EasyPermissions library in Android with Java. We will create a simple application that checks camera permission, requests access when necessary, and handles permission denial.
We will also discuss modern Android permission APIs, Android 13 and later changes, and best practices for building privacy-friendly applications.
What Is EasyPermissions?
EasyPermissions is an Android library that simplifies runtime permission handling.
It provides helper methods for checking permissions, requesting permissions, handling permission callbacks, displaying rationale messages, and directing users to application settings when appropriate.
It can help developers:
Request one or multiple permissions.
Check whether permissions have been granted.
Explain why a permission is needed.
Handle permission-denial callbacks.
Direct users to app settings when necessary.
EasyPermissions is a convenience library, not a replacement for Android's permission system. The operating system still controls whether access is granted.
Why Runtime Permissions Matter
Android permissions protect sensitive information and device capabilities.
Examples include:
Camera: Capturing photos or video.
Location: Accessing device location.
Microphone: Recording audio.
Contacts: Accessing the user's contacts.
Media access: Reading certain images, videos, or other files.
Not every permission follows the same rules. Some permissions are granted at installation, while others require runtime approval. The exact requirements depend on the permission, Android version, target SDK, and how the feature is implemented.
For example, an application that only launches the system camera to capture an image may not need direct camera permission. However, an application that accesses the camera hardware itself generally does.
Step 1: Add the EasyPermissions Dependency
Open your app-level build.gradle file and add the following dependency:
dependencies {
implementation 'pub.devrel:easypermissions:3.0.0'
}If your project uses Kotlin DSL, the equivalent is:
dependencies {
implementation("pub.devrel:easypermissions:3.0.0")
}Sync your Gradle project after adding the dependency.
This tutorial uses Java and the EasyPermissions 3.0.0 API. Before adopting a library in a production project, verify its maintenance status and compatibility with your Android Gradle Plugin, SDK versions, and dependency policies.
Step 2: Declare Camera Permission
Open AndroidManifest.xml and add the camera permission inside the manifest element, outside the application element:
<uses-permission
android:name="android.permission.CAMERA" />Do not automatically add storage permissions just because your application uses the camera.
On modern Android versions, READ_EXTERNAL_STORAGE is not a universal solution for file or photo access. Android 13 introduced granular media permissions for certain shared-media access scenarios, and Android's system photo picker can often be used without requesting broad media-library permission.
For this tutorial, we will request only camera permission because the example demonstrates checking and requesting camera access.
Step 3: Create the Layout
Create or update activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_open_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Check Camera Permission"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>This layout contains a button that starts the permission workflow.
Step 4: Implement Permission Handling in MainActivity.java
Create MainActivity.java:
package com.example.easypermissionexample;
import android.Manifest;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.AppSettingsDialog;
import pub.devrel.easypermissions.EasyPermissions;
public class MainActivity extends AppCompatActivity
implements EasyPermissions.PermissionCallbacks {
private static final int CAMERA_REQUEST_CODE = 123;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button buttonOpenCamera =
findViewById(R.id.button_open_camera);
buttonOpenCamera.setOnClickListener(
view -> requestCameraPermission()
);
}
@AfterPermissionGranted(CAMERA_REQUEST_CODE)
private void requestCameraPermission() {
String[] permissions = {
Manifest.permission.CAMERA
};
if (EasyPermissions.hasPermissions(
this,
permissions
)) {
onCameraPermissionReady();
} else {
EasyPermissions.requestPermissions(
this,
"Camera access is needed for the camera feature.",
CAMERA_REQUEST_CODE,
permissions
);
}
}
private void onCameraPermissionReady() {
Toast.makeText(
this,
"Camera permission granted",
Toast.LENGTH_SHORT
).show();
// Launch your camera feature here.
// This example checks permission only;
// it does not open the camera.
}
@Override
public void onPermissionsGranted(
int requestCode,
@NonNull List<String> permissions
) {
if (requestCode == CAMERA_REQUEST_CODE) {
onCameraPermissionReady();
}
}
@Override
public void onPermissionsDenied(
int requestCode,
@NonNull List<String> permissions
) {
if (requestCode != CAMERA_REQUEST_CODE) {
return;
}
Toast.makeText(
this,
"Camera permission was denied",
Toast.LENGTH_SHORT
).show();
if (EasyPermissions.somePermissionPermanentlyDenied(
this,
permissions
)) {
new AppSettingsDialog.Builder(this)
.build()
.show();
}
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults
) {
super.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
);
EasyPermissions.onRequestPermissionsResult(
requestCode,
permissions,
grantResults,
this
);
}
}How the implementation works
The user taps the button.
The application checks whether camera permission has already been granted.
If permission is available, the application continues to the camera-feature placeholder.
If permission is missing, EasyPermissions requests it through Android.
The callback handles the permission result.
If the permission has been permanently denied according to the library's detection logic, the application offers a route to settings.
The example intentionally does not open the camera. Actual camera functionality requires additional implementation using an appropriate Android camera API or camera intent.
Step 5: Understand the EasyPermissions Annotations
@AfterPermissionGranted
The @AfterPermissionGranted annotation can run an annotated method after the associated permission request succeeds, provided the method is invoked through the EasyPermissions workflow.
In the example, the method checks the permission before continuing. The success callback also invokes the camera-feature placeholder when permission is granted.
For a production implementation, keep the success path simple and avoid triggering the same feature twice if both the annotation and callback logic invoke it. Choose one clear continuation pattern and test it against the library version in use.
EasyPermissions.hasPermissions()
This method checks whether the requested permissions are currently granted.
if (EasyPermissions.hasPermissions(
this,
Manifest.permission.CAMERA
)) {
// Continue with the feature.
}Always check permission before accessing a protected capability. Permission status can change while the application is not in use.
EasyPermissions.requestPermissions()
This method initiates the runtime permission request:
EasyPermissions.requestPermissions(
this,
"Camera access is needed for this feature.",
CAMERA_REQUEST_CODE,
Manifest.permission.CAMERA
);The explanation should accurately describe why the application needs the permission. Avoid requesting unrelated permissions at the same time.
AppSettingsDialog
When a user has denied a permission in a way that prevents another ordinary request, EasyPermissions can help display a dialog directing the user to application settings.
new AppSettingsDialog.Builder(this)
.build()
.show();Android's exact denial behavior varies by version and permission history. Do not assume that every denial means the user has permanently blocked future requests.
Step 6: Handle Permission Denial Properly
A permission-denial state should not make the application crash or become unusable.
When a user denies camera access:
Explain which feature is unavailable.
Allow the user to continue using unrelated parts of the application.
Ask again only when the permission is needed and the request is appropriate.
Provide a settings shortcut when necessary.
Do not repeatedly show permission prompts without a meaningful user action.
A good permission flow respects the user's decision and provides a useful fallback.
Step 7: Understand Android Version Differences
Permission handling evolves across Android releases, so applications must account for their target SDK and the permissions they actually use.
Android 6.0 and later
Dangerous permissions require runtime handling for applications operating under the relevant runtime-permission rules.
Android 10 and later
Scoped storage changed how applications access shared files. Broad filesystem access should not be assumed.
Android 11 and later
Users may encounter additional permission restrictions, and Android can automatically reset certain permissions for unused applications.
Android 13 and later
Applications that need to read shared images, videos, or audio may need the corresponding granular media permissions, depending on their use case and target SDK.
Android 13 also introduced the notification runtime permission for applicable applications.
Android 14 and later
Some access flows, including selected-photo access, have additional behavior. Applications should follow the current platform guidance for the specific feature rather than relying on old storage-permission examples.
Important: Camera permission, media-library access, photo-picker access, and notification permission are different use cases. Do not combine them into a single permission request unless the feature genuinely needs them.
Step 8: Modern Android Permission Recommendations
EasyPermissions can simplify existing Java codebases, but new Android projects should also understand the platform's Activity Result APIs.
For example, the platform camera permission can be requested with RequestPermission:
private final ActivityResultLauncher<String>
cameraPermissionLauncher =
registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
isGranted -> {
if (isGranted) {
// Continue with the camera feature.
} else {
// Explain the unavailable feature.
}
}
);Add the required AndroidX Activity Result imports and register the launcher before the Activity or Fragment is created. Invoke cameraPermissionLauncher.launch(Manifest.permission.CAMERA) when the user requests the camera feature.
This approach avoids the older onRequestPermissionsResult() callback pattern for that permission flow.
Use EasyPermissions if it fits an existing project and your dependency requirements. For a new implementation, the platform Activity Result APIs are a strong option.
EasyPermissions vs Activity Result APIs
| Feature | EasyPermissions | Activity Result APIs |
|---|---|---|
| Library | Third-party dependency | AndroidX platform integration |
| Permission checks | Helper methods | Platform permission checks |
| Request handling | Library callbacks | Registered result callbacks |
| Rationale | Library support | Application-defined UI |
| Settings redirection | Helper dialog available | Application-defined navigation |
| Best fit | Existing projects using the library | Modern Android permission flows |
Both approaches still rely on Android's permission system. Neither guarantees that a user will grant access.
Common Mistakes Developers Make
1. Forgetting manifest declarations
Declare the relevant permission in AndroidManifest.xml when the feature requires it.
2. Requesting unnecessary permissions
Ask only for the permissions needed for the feature being used. Consider Android's system photo picker or other privacy-preserving alternatives when possible.
3. Treating every denial as permanent
The user's ability to be prompted again depends on the Android version and the permission request history.
4. Assuming permission approval guarantees feature success
A granted permission does not guarantee that the device has the required hardware, that the camera is available, or that a subsequent operation will succeed.
5. Ignoring application settings changes
Users can change permissions in settings. Recheck permission status before using the protected feature, including when the user returns to the application.
6. Using outdated storage permissions
Do not copy READ_EXTERNAL_STORAGE into every modern Android manifest. Choose the appropriate API for the intended file or media access.
Frequently Asked Questions
Is EasyPermissions still useful in 2026?
It can still be useful in existing Android applications that rely on it. Before adopting it in a new project, check its current maintenance status and compatibility with your dependencies.
Can EasyPermissions request multiple permissions?
Yes. The library supports requesting multiple permissions. Request them together only when they are genuinely needed for the same user-facing operation.
What happens if the user permanently denies a permission?
Depending on Android's behavior and the permission history, another standard request may not display the normal prompt. The application can explain the limitation and direct the user to settings when appropriate.
Do all Android permissions require runtime requests?
No. Permission requirements vary. Some permissions are granted at installation, some require runtime approval, and some capabilities can be accessed through system-mediated flows without granting broad access.
Should I use EasyPermissions or the Activity Result API?
For an existing Java project that already uses EasyPermissions, keeping the library may be reasonable. For a new permission flow, AndroidX Activity Result APIs are a modern alternative.
Conclusion
Runtime permissions are essential for protecting user privacy and controlling access to sensitive Android capabilities.
EasyPermissions can make permission checks, requests, rationale messages, and denial handling easier to organize in Java applications. However, developers must still follow Android's permission rules, request only necessary access, and handle denial gracefully.
For modern Android projects, consider the Activity Result APIs and current privacy-preserving platform features. Choosing the right permission strategy improves security, maintainability, and the overall user experience.
No comments:
Post a Comment