Requesting all permissions on app launch is one of the most reliable ways to tank user retention. Apple's Human Interface Guidelines and GDPR Art. 5(1)(c) both require that data access be tied to a specific user action and purpose — not collected speculatively. When a user sees a camera permission request before they have navigated anywhere near a camera feature, they have no frame of reference for why the app needs it, so they deny. On Android, Google Play reviews flag apps that request permissions at launch without clear justification, and repeated violations result in policy strikes. Any permission requested in a root-level useEffect(() => {}, []) is a request that was never triggered by user intent.
High because launch-time permission requests eliminate the user's ability to understand the purpose of the request, directly reducing grant rates and creating GDPR Art. 5(1)(c) data-minimisation violations.
Move every permission request inside the component or handler for the feature that needs it. Remove all permission calls from root-level useEffect hooks and app bootstrap sequences.
// Remove this pattern from app entry points:
// useEffect(() => { requestCameraPermission() }, [])
// Add this pattern inside the feature component:
function VideoCallScreen() {
const handleStartCall = async () => {
const result = await request(
Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA
)
if (result === RESULTS.GRANTED) {
startCall()
}
}
return <Button onPress={handleStartCall} title="Start Video Call" />
}
For onboarding flows: only request a permission when the user explicitly enables the feature that requires it, not as a bulk step at the end of account setup.
ID: mobile-permissions-privacy.permission-requests.point-of-use
Severity: high
What to look for: Enumerate all permission request calls in the codebase. For each, classify whether it is triggered by user action (point-of-use) or by app lifecycle (launch, onboarding). Check root component useEffect blocks and app bootstrap code for premature requests.
Pass criteria: At least 100% of permission requests happen when the user navigates to a feature that requires the permission (not during app launch or onboarding). OR if requested during onboarding, they are clearly tied to features the user is about to set up.
Fail criteria: Any permission is requested on app launch with no user action to trigger it. OR the app asks for all permissions upfront in onboarding before any features are used. Must not pass when permissions are requested in root useEffect(() => {}, []) blocks.
Skip (N/A) when: App requests no permissions (no permission request calls found anywhere in code).
Detail on fail: Name the permissions requested prematurely and quote the trigger code. Example: "Location permission requested in app.js useEffect on every app launch, before user has navigated to location-dependent feature" or "Contacts permission requested during onboarding even if user skips contacts feature"
Remediation: Request permissions when the user is about to use a feature, not when the app starts. This improves user trust and grant rates:
// Bad: Request on launch
useEffect(() => {
requestCameraPermission()
}, [])
// Good: Request when user navigates to camera feature
function VideoCalls() {
const [cameraGranted, setCameraGranted] = useState(false)
const startCall = async () => {
const result = await request(PERMISSIONS.IOS.CAMERA)
if (result === RESULTS.GRANTED) {
setCameraGranted(true)
// start video call
}
}
return <Button onPress={startCall} title="Start Call" />
}