Android M
Permissions
onRequestPermissionsResult
Debugging
App Development

Android M Permissions onRequestPermissionsResult not being called

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A frequent Android runtime-permission issue is requesting permissions successfully but never receiving onRequestPermissionsResult. This usually happens when the request is sent from one component and the callback is expected in another, or when modern APIs are mixed with legacy callback flows. On Android 6.0 and newer, permissions are asynchronous, so wiring mistakes are easy to miss until users deny or grant at runtime. This guide explains why callbacks disappear, how to fix classic requestPermissions usage, and why ActivityResultContracts is now the safer long-term solution.

Understand the Callback Path

For legacy APIs, the callback returns to the same Activity or Fragment that initiated the request. If a Fragment calls through the Activity incorrectly, the fragment callback may never trigger.

java
1// Correct in Fragment
2requestPermissions(
3    new String[]{Manifest.permission.CAMERA},
4    REQ_CAMERA
5);

If your Activity is making the request, handle the callback there. If your Fragment is making it, handle it in that fragment. Also ensure request codes are unique and not overwritten.

java
1@Override
2public void onRequestPermissionsResult(int requestCode,
3                                       @NonNull String[] permissions,
4                                       @NonNull int[] grantResults) {
5    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
6    if (requestCode == REQ_CAMERA) {
7        boolean granted = grantResults.length > 0
8            && grantResults[0] == PackageManager.PERMISSION_GRANTED;
9        // Continue or show fallback UI
10    }
11}

Prefer ActivityResultContracts for New Code

onRequestPermissionsResult still works, but registerForActivityResult is less error-prone and lifecycle-aware.

java
1private final ActivityResultLauncher<String> cameraPermissionLauncher =
2    registerForActivityResult(
3        new ActivityResultContracts.RequestPermission(),
4        isGranted -> {
5            if (isGranted) {
6                startCamera();
7            } else {
8                showCameraDeniedMessage();
9            }
10        }
11    );
12
13private void requestCameraPermission() {
14    cameraPermissionLauncher.launch(Manifest.permission.CAMERA);
15}

This approach avoids manual request codes and reduces callback routing bugs across nested fragments.

Fragment and Activity Integration Rules

Many issues come from mixed ownership. Keep ownership explicit.

java
1// Activity hosts fragment
2Fragment f = getSupportFragmentManager().findFragmentByTag("camera");
3
4// Bad: activity requests permission for fragment logic
5// Good: fragment requests permission itself and handles result itself

If you must centralize permission handling in the activity, expose a callback interface to the fragment. Do not expect both components to automatically receive the same result unless you explicitly forward it.

Also check that you are using AndroidX fragments consistently. Mixing old support fragments and AndroidX fragments can break callback dispatch.

Debugging Checklist

Use logs around request and callback points to confirm control flow:

java
1Log.d("Perm", "Requesting camera permission");
2requestPermissions(new String[]{Manifest.permission.CAMERA}, REQ_CAMERA);
3
4@Override
5public void onRequestPermissionsResult(...) {
6    Log.d("Perm", "Callback arrived for requestCode=" + requestCode);
7}

If callback never logs:

  • Verify the request method actually runs.
  • Verify component is still attached when callback arrives.
  • Confirm no crash or navigation happens before callback.
  • Confirm permission is in AndroidManifest.xml.

Practical Verification Workflow

A reliable way to avoid regressions is to validate the solution in three passes: baseline, controlled change, and repeatability check. First, capture a baseline outcome before you apply fixes. This could be a failing command, a wrong output sample, a stack trace, or a screenshot of current behavior. Second, apply one focused change and rerun exactly the same checks so you can attribute improvements to a specific edit. Third, rerun the checks multiple times or with slightly different inputs to ensure the fix is not accidental or data-specific.

A lightweight template you can adapt for most projects looks like this:

bash
1# 1) reproduce current behavior
2./run_example.sh > before.txt
3
4# 2) apply your change
5# edit config/code based on this article
6
7# 3) verify behavior after change
8./run_example.sh > after.txt
9diff -u before.txt after.txt

If your environment involves tests, add at least one focused regression test that would fail before the fix and pass after it. This turns a one-time troubleshooting success into a durable maintenance improvement, which is especially important when teams rotate ownership or upgrade dependencies later.

Common Pitfalls

  • Calling ActivityCompat.requestPermissions in an activity while expecting fragment callback methods to fire.
  • Using the same request code for multiple permission flows and branching incorrectly.
  • Forgetting to call super.onRequestPermissionsResult in complex fragment hierarchies.
  • Mixing old support fragments with AndroidX fragments in the same screen.
  • Triggering navigation immediately after request, destroying the component before callback execution.

Summary

When onRequestPermissionsResult is not called, the root cause is usually callback ownership or lifecycle mismatch. Keep request and handling in the same component, use unique request identifiers, and migrate new code to ActivityResultContracts. With clear ownership and lifecycle-safe APIs, runtime permission handling becomes predictable and easier to maintain.


Course illustration
Course illustration

All Rights Reserved.