Request permission is not resolving in Android 16 if the status is never_ask_again
Before submitting a new issue
- [x] I tested using the latest version of the library, as the bug might be already fixed.
- [x] I tested using a supported version of react native.
- [x] I checked for possible duplicate issues, with possible answers.
Bug summary
On Android 16, when using react-native-permissions (latest version) to check or request permissions, the promise does not resolve if the permission is in a blocked state.
-
Works fine for granted and denied states.
-
Hangs (no resolution) for blocked.
-
Issue reproduced with all permissions (check with ACCESS_FINE_LOCATION and CAMERA)
On Android 16, app must be moved foreground -> background → foreground to trigger callback in never_ask_again case
Android version: 16 Build number: BP41.250822.010
Library version
5.4.2
Environment info
System:
OS: macOS 15.7
CPU: (14) arm64 Apple M4 Pro
Memory: 740.94 MB / 48.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 22.19.0
path: /var/folders/hv/4xscjh9n527786cfrr112q_80000gn/T/yarn--1758535287705-0.9384704201405711/node
Yarn:
version: 1.22.22
path: /var/folders/hv/4xscjh9n527786cfrr112q_80000gn/T/yarn--1758535287705-0.9384704201405711/yarn
npm:
version: 10.9.3
path: /opt/homebrew/opt/node@22/bin/npm
Watchman:
version: 2025.09.15.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 24.2
- iOS 18.2
- macOS 15.2
- tvOS 18.2
- visionOS 2.2
- watchOS 11.2
Android SDK:
API Levels:
- "35"
- "36"
Build Tools:
- 35.0.0
- 35.0.1
- 36.0.0
System Images:
- android-35 | Pre-Release 16 KB Page Size Google Play ARM 64 v8a
- android-36 | Google Play ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2024.3 AI-243.26053.27.2432.13536105
Xcode:
version: 16.2/16C5032a
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.16
path: /opt/homebrew/opt/openjdk@17/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.0.0
wanted: 20.0.0
react:
installed: 19.1.0
wanted: 19.1.0
react-native:
installed: 0.81.4
wanted: 0.81.4
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: false
Steps to reproduce
Install react-native-permissions (latest).
Block location permission manually in app settings.
Run the following code:
import { request, PERMISSIONS } from "react-native-permissions";
const test = async () => { const result = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION); console.log("result:", result); // never resolves if blocked };
Reproducible sample code
import React, { useState } from "react";
import { View, Text, Button, StyleSheet } from "react-native";
import RNPermissions, { PERMISSIONS, PermissionStatus } from "react-native-permissions";
const App = () => {
const [status, setStatus] = useState<PermissionStatus | string>("Unknown");
const requestLocationPermissionLib = async () => {
try {
const result = await RNPermissions.request(
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
{
title: "Location Permission",
message: "This app needs access to your location.",
buttonNegative: "Cancel",
buttonPositive: "OK",
}
);
setStatus(result);
} catch (err) {
console.warn(err);
setStatus("error");
}
};
const checkLocationPermissionLib = async () => {
try {
const result = await RNPermissions.check(
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
);
setStatus(result);
} catch (err) {
console.warn(err);
setStatus("error");
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Location Permission Example</Text>
<Text style={styles.status}>Current Status: {status}</Text>
<View style={styles.buttonContainer}>
<Button title="Check Permission lib" onPress={checkLocationPermissionLib} />
</View>
<View style={styles.buttonContainer}>
<Button title="Request Permission lib" onPress={requestLocationPermissionLib} />
</View>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center", padding: 16, backgroundColor: "white" },
title: { fontSize: 20, fontWeight: "bold", marginBottom: 20 },
status: { fontSize: 16, marginBottom: 20 },
buttonContainer: { marginVertical: 8, width: "80%" },
});
export default App;
facebook/react-native#53887
Getting the same on Android 16
Same. It has to be Android 16 QPR1.
Same on Android 16, react native 0.79.6
Same on Android 15, react native 0.80.1
I think we need to wait for this fix https://github.com/facebook/react-native/pull/53898
FYI @kanthivel. This is my implementation for addressing the permission issue on Android.
Context
I encountered a “Not resolved” state when users denied the two system dialogs triggered by requestMultiple(). After the first two denials, the system dialog stops appearing entirely.
Implementation
public static async location(): Promise<void | boolean> {
const checkSystemPermissions =
await this.handleCheckSystemPermissionSetting([
ActionLocation.UNIVERSAL_LOCATION,
]);
if (checkSystemPermissions.length > 0) {
this.handleOpenPermissionModalMultiple(checkSystemPermissions);
} else {
if (Platform.OS === 'ios') {
return this._fnBatchImplIOS([
{
permissionId: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
},
]);
} else if (Platform.OS === 'android') {
return this._fnBatchImplAndroid([
{
permissionId: PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
apiLevel: 23,
},
{
permissionId: PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
apiLevel: 23,
},
]);
}
}
}
private static async _fnBatchImplAndroid(
batch: PermissionsBatch_ANDROID,
): Promise<void | boolean> {
if (Platform.OS !== 'android') {
return;
}
return new Promise((resolve, reject) => {
PermissionsAndroid.requestMultiple(batch.map(i => i.permissionId)).then(
(_result: { [key: string]: PermissionStatus }) => {
const platformContants = (Platform as PlatformAndroidStatic).constants;
const deniedPermissions: PermissionRejectError[] = [];
const _fnCheckPermission = (permission: string): null | string => {
if (_result[permission] === 'granted') {
return null;
}
const rejectObject: PermissionRejectError = {
os: Platform.OS,
permissionType: this.mapPermissionToActionLocation(permission),
reason: _result[permission],
settingLevel: 'app',
apiLevel: platformContants.Version,
};
deniedPermissions.push(rejectObject);
return _result[permission];
};
for (const item of batch) {
if (platformContants.Version >= item.apiLevel) {
_fnCheckPermission(item.permissionId);
}
}
if (deniedPermissions.length > 0) {
reject(deniedPermissions); // Reject with all denied permissions
} else {
resolve(true);
}
},
);
});
}
Final Approach
After detecting that the user has denied the permission twice—and the system dialog will no longer appear—I switch to using check() to determine the permission state. If the permission is still denied and the system dialog has already appeared twice, the app displays our custom permission modal instead.
This results in a significantly faster, more predictable, and more consistent permission flow.