Build the mental model
Mobile apps can tap into device capabilities like camera, location, and notifications, but every one requires the user's explicit permission first. The OS ultimately decides whether a request is granted.
The universal architecture: feature needs capability -> explain in context -> request permission -> allowed means the feature works, denied means graceful fallback.
Avoid requesting at launch
Requesting every permission at app launch overwhelms users with decisions they have no context for yet, often leading to reflexive denial.
Denial is not a failure state to crash on — a location denial should disable location-dependent features while keeping the rest of the app usable.
PERMISSION REQUEST FLOW
-----------------------
PERMISSION REQUEST FLOW
-----
Feature needs a capability (camera, location, etc.)
|
v
Explain why, in context (not at app launch)
|
v
Request permission from the OS
|
+----+----+
| |
Allowed Denied
| |
v v
Feature Graceful fallback
works (not a broken feature)Connect it to a real scenario
Audit every permission request an app makes — is it tied to a specific action, does it include an explanation, and what happens if the user says no?
An app that requests camera access at launch with no explanation trains reflexive "Deny" taps, while the same request triggered on tap with a one-line explanation gets granted far more often.
Graceful fallback is the other half of good practice — a delivery app can let users type an address manually instead of showing a broken map when location is denied.
Permission Request Best Practices
Try the working example
function followsPermissionBestPractice(request) {
const { isRequestedInContext, hasExplanation, isRequestedAtLaunch } = request;
const issues = [];
if (!isRequestedInContext) issues.push("not requested in the context of actual feature use");
if (!hasExplanation) issues.push("no explanation given before requesting");
if (isRequestedAtLaunch) issues.push("requested at app launch instead of when needed");
return { followsBestPractice: issues.length === 0, issues };
}
const cameraRequestDoneWell = {
isRequestedInContext: true,
hasExplanation: true,
isRequestedAtLaunch: false
};
const locationRequestDonePoorly = {
isRequestedInContext: false,
hasExplanation: false,
isRequestedAtLaunch: true
};
console.log("Camera request done well:", JSON.stringify(followsPermissionBestPractice(cameraRequestDoneWell)));
console.log("Location request done poorly:", JSON.stringify(followsPermissionBestPractice(locationRequestDonePoorly), null, 2));Camera request done well: {"followsBestPractice":true,"issues":[]}
Location request done poorly: {
"followsBestPractice": false,
"issues": [
"not requested in the context of actual feature use",
"no explanation given before requesting",
"requested at app launch instead of when needed"
]
}
The well-designed request has zero issues, while the blind, unexplained, at-launch request triggers all three.5-minute try-it
Rewrite `followsPermissionBestPractice` so it also checks a fourth flag, `deniedPreviously`, and downgrades a repeated at-launch request to an even stronger warning — then test it against a request that was already denied once.
One important caution
Requesting every permission (camera, location, notifications) all at once at app launch
Letting a single denied permission crash or disable the entire app instead of just the dependent feature
Permissions overview — Android Developers — How Mobile Apps Work