npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-permission-flow

v0.1.25

Published

A developer-friendly permissions library for Expo and React Native. One call handles the full workflow — check, request, wait for app resume, return final status.

Readme

react-native-permission-flow

[!WARNING] Migration Notice: This package was formerly known as @abrarmehraj/permission-kit. If you are upgrading, please uninstall the old package and install react-native-permission-flow. Update all imports in your code and the plugin name in your app.json.

A developer-friendly permissions library for Expo and React Native.

One call handles the full workflow — check, request, wait for app resume, return final status.

const result = await PermissionKit.notifications();
// { status: 'granted' } or { status: 'denied' }

React Hooks API ✨

For React components, PermissionKit provides a suite of custom hooks. They handle everything for you: checking permissions on mount, listening to app state changes (e.g. when the app resumes from the background), and managing native loading states.

import { 
  useLocation, 
  useMedia, 
  useNotifications, 
  useBatteryOptimization,
  useOverlay,
  useUsageStats,
  useExactAlarm,
  useFullScreenIntent,
  useAccessibility,
  useDndAccess
} from 'react-native-permission-flow';

Standard Hook Return Object

All hooks return the exact same predictable object interface:

const { 
  status,     // String: 'granted' | 'denied' | 'unavailable' | 'restricted'
  success,    // Boolean: true if status === 'granted'
  request,    // Function: Trigger the OS permission prompt
  check,      // Function: Manually re-check the status
  isLoading,  // Boolean: true while the OS prompt is open or data is fetching
  result      // Object: The full raw result payload (useful for Location coordinates)
} = useLocation();

Passing Options to Hooks

Hooks accept the exact same options as their async function counterparts!

// Example: Requesting photos with native iOS "limited" prompt
const photoPerm = useMedia({ type: 'photo', requestMore: true });

// Example: Location with high accuracy & GPS fetching
const locationPerm = useLocation({ 
  fetchCoordinates: true, 
  accuracy: 'high', 
  timeout: 15000 
});

// Example: Checking for a specific Accessibility Service
const a11yPerm = useAccessibility({ 
  androidServicePath: '.MyAccessibilityService' 
});

Full Example

import { Button, Text, View } from 'react-native';
import { useLocation } from 'react-native-permission-flow';

export default function MyComponent() {
  const { status, success, request, isLoading, result } = useLocation({ fetchCoordinates: true });

  return (
    <View>
      <Text>Status: {status.toUpperCase()}</Text>
      
      {success && result && 'latitude' in result && (
        <Text>📍 Lat: {result.latitude}, Lng: {result.longitude}</Text>
      )}

      <Button 
        title={isLoading ? "Please wait..." : (success ? "Granted!" : "Request Location")} 
        onPress={request} 
        disabled={isLoading || success}
      />
    </View>
  );
}

Installation

npm install react-native-permission-flow
# or
yarn add react-native-permission-flow
# or
pnpm add react-native-permission-flow

Setup

For Expo Projects (Recommended)

Use the built-in Config Plugin to automatically handle Android permissions. This ensures you only request the permissions you actually use!

In your app.json, add the plugin and specify the permissions you want:

{
  "expo": {
    "plugins": [
      [
        "react-native-permission-flow",
        {
          "permissions": ["batteryOptimization", "overlay", "usageStats", "exactAlarm", "fullScreenIntent", "dndAccess", "notifications", "location", "media:photo", "media:video"],
          "locationDescription": "Used to show your current position.",
          "photoDescription": "Used to pick a profile picture.",
          "appleMusicDescription": "Used to pick audio."
        }
      ]
    ]
  }
}
  • permissions: Array of permissions you intend to use. Only the required ones will be injected into AndroidManifest.xml.

    Available permission keys: | Key | What it does | |---|---| | batteryOptimization | Exempt from battery optimization | | overlay | Draw over other apps | | usageStats | App usage statistics | | exactAlarm | Schedule exact alarms (user-revocable, for general apps) | | useExactAlarm | Auto-granted exact alarms (for alarm clock / timer apps only — requires Play Store justification) | | fullScreenIntent | Full screen intents | | dndAccess | Do Not Disturb access | | notifications | Push & local notifications | | location | Fine + coarse location | | media | All media permissions (photos, video, audio, all-files) — backward compatible | | media:photo | Only photo library access | | media:video | Only video library access | | media:audio | Only music/audio library access | | media:all | Full file manager access (MANAGE_EXTERNAL_STORAGE) |

    Tip: Use granular media:photo, media:video, etc. instead of bare media to avoid declaring unnecessary permissions in your manifest. This helps with Play Store reviews.

  • locationDescription: (Optional) For iOS, this string is used as the NSLocationWhenInUseUsageDescription. Defaults to "$(PRODUCT_NAME) needs access to your location."

  • photoDescription: (Optional) For iOS, this string is used as the NSPhotoLibraryUsageDescription. Defaults to "$(PRODUCT_NAME) needs access to your photos."

  • appleMusicDescription: (Optional) For iOS, this string is used as the NSAppleMusicUsageDescription. Defaults to "$(PRODUCT_NAME) needs access to your music."

Then run:

npx expo prebuild

For Bare React Native Projects (Without Expo Prebuild)

If you are not using Expo Prebuild, you must manage your AndroidManifest.xml manually.

Add the required permissions to your android/app/src/main/AndroidManifest.xml ONLY for the features you intend to use:

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- OR, if you are building an alarm clock app: -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />

<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Media Permissions for all API Levels -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

Note: PermissionKit requires Expo Modules architecture. If you are on React Native 0.69+, you likely already have it. Make sure you run npx pod-install for iOS.

API

PermissionKit.media(options)

Automatically resolves Android fragmentation (Android 14+, Android 13, Android < 13) and iOS restricted modes into a simple API.

import { PermissionKit } from 'react-native-permission-flow';

// type: 'photo' | 'video' | 'audio' | 'all'
// requestMore (optional): Set to true to pop up the native OS picker when status is 'limited' so users can add more photos.
// showAlert (optional): If true, automatically shows a native alert if permanently denied. Default: true.
// alertTitle / alertDescription (optional): Custom text for the settings fallback alert.
const result = await PermissionKit.media({ 
  type: 'photo', 
  requestMore: true,
  showAlert: true 
});

if (result.status === 'granted') {
  // You have full access. Safe to open your image picker.
} else if (result.status === 'limited') {
  // User only granted access to a few specific photos (Android 14+ / iOS 14+).
  // Because we passed requestMore: true, the OS prompt will appear first so they can select more photos.
} else if (result.status === 'denied') {
  // Permission denied. Use this to show a custom UI before calling PermissionKit.openMediaSettings()
} else if (result.status === 'unavailable') {
  // E.g., iOS doesn't have an "all" files permission (use Document Picker instead).
}

Note: Requesting type: 'all' on Android will trigger the MANAGE_EXTERNAL_STORAGE settings redirect natively. On iOS, it returns unavailable.

PermissionKit.checkMedia({ type })

Check the current media permission status.

const result = await PermissionKit.checkMedia({ type: 'photo' });

PermissionKit.batteryOptimization()

Checks if battery optimization is disabled for your app. If not, automatically opens the Android Settings dialog and waits for the user to return. Re-checks on resume.

import { PermissionKit } from 'react-native-permission-flow';

const result = await PermissionKit.batteryOptimization();

if (result.status === 'granted') {
  // App is excluded from battery optimization
} else if (result.status === 'denied') {
  // User denied
} else if (result.status === 'unavailable') {
  // Platform doesn't support this (e.g., iOS)
}

PermissionKit.checkBatteryOptimization()

Check the current status without showing any dialog or opening settings.

const result = await PermissionKit.checkBatteryOptimization();
// Use on app start to know the current state

PermissionKit.overlay()

Checks if the app is allowed to draw over other apps (System Alert Window). If not, automatically opens the Android "Display over other apps" settings, waits for the user to return, and re-checks on resume.

const result = await PermissionKit.overlay();

if (result.status === 'granted') {
  // App is allowed to draw over others
}

PermissionKit.checkOverlay()

Check the current overlay status without opening settings.

const result = await PermissionKit.checkOverlay();

PermissionKit.usageStats()

Checks if the app is allowed to access app usage statistics (Usage Access). Required for app blockers or digital wellbeing features. If not, automatically opens the Android "Usage Access" settings, waits for the user to return, and re-checks on resume.

const result = await PermissionKit.usageStats();

if (result.status === 'granted') {
  // App is allowed to read usage stats
}

PermissionKit.checkUsageStats()

Check the current usage access status without opening settings.

const result = await PermissionKit.checkUsageStats();

PermissionKit.exactAlarm()

Checks if the app is allowed to schedule exact alarms (Android 14+ requirement).

  • If you configured "exactAlarm" in your setup: Automatically opens the Android "Alarms & Reminders" settings, waits for the user to toggle it on, and re-checks on resume.
  • If you configured "useExactAlarm" in your setup: This will instantly return { status: 'granted' } without opening any settings, since it is auto-granted by Android.
const result = await PermissionKit.exactAlarm();

if (result.status === 'granted') {
  // App is allowed to schedule exact alarms
}

PermissionKit.checkExactAlarm()

Check the current exact alarm status without opening settings.

const result = await PermissionKit.checkExactAlarm();

PermissionKit.fullScreenIntent()

Checks if the app is allowed to use full-screen intents (Android 14+ requirement). If not, automatically opens the Android "Full screen intents" settings, waits for the user to return, and re-checks on resume.

const result = await PermissionKit.fullScreenIntent();

if (result.status === 'granted') {
  // App is allowed to use full screen intents
}

PermissionKit.checkFullScreenIntent()

Check the current full screen intent status without opening settings.

const result = await PermissionKit.checkFullScreenIntent();

PermissionKit.accessibility({ androidServicePath })

Checks if a specific Accessibility Service is enabled. If not, automatically opens the Android Accessibility Settings, waits for the user to return, and re-checks on resume.

Note: Your app must actually define an Accessibility Service in its AndroidManifest.xml to appear in the settings list!

const result = await PermissionKit.accessibility({
  androidServicePath: "com.myapp.MyAccessibilityService" // or ".MyAccessibilityService"
});

if (result.status === 'granted') {
  // Service is active
}

PermissionKit.checkAccessibility({ androidServicePath })

Check the current accessibility service status without opening settings.

const result = await PermissionKit.checkAccessibility({
  androidServicePath: ".MyAccessibilityService"
});

PermissionKit.dndAccess()

Checks if the app is allowed to modify Do Not Disturb (Notification Policy Access). If not, automatically opens the Android "Do Not Disturb access" settings, waits for the user to return, and re-checks on resume.

const result = await PermissionKit.dndAccess();

if (result.status === 'granted') {
  // App can now mute the phone or change DND rules
}

PermissionKit.checkDndAccess()

Check the current DND access status without opening settings.

const result = await PermissionKit.checkDndAccess();

PermissionKit.notifications(opts?: NotificationOptions)

Requests notification permission from the user using the correct industry-standard flow:

  • First call: Shows the native OS permission dialog (on both iOS and Android 13+).
  • User taps "Allow": Returns { status: 'granted' }.
  • User taps "Don't Allow": Returns { status: 'denied', canAskAgain: true }. The user's choice is respected — no forced redirect.
  • Subsequent call (after permanent denial): Returns { status: 'denied', canAskAgain: false }. If you pass showAlertConfig: true, it will automatically show a native alert explaining why the permission is needed and provide an "Open Settings" button that redirects the user directly to your app's Notification settings.
const result = await PermissionKit.notifications({
  showAlertConfig: true,
  alertConfig: {
    title: 'Notifications Required',
    description: 'Please enable notifications in Settings to stay updated.',
  }
});

if (result.status === 'granted') {
  // Notifications are enabled — schedule your push token registration here
} else if (result.status === 'denied') {
  // User denied — show an in-app explanation if you want
} else if (result.status === 'unavailable') {
  // Should not happen on iOS or Android 13+
}

PermissionKit.checkNotifications()

Check the current notification permission status without showing any dialog.

const result = await PermissionKit.checkNotifications();
// { status: 'granted' | 'denied', canAskAgain: boolean }

PermissionKit.location(opts?: LocationOptions)

Requests precise location permission from the user and fetches the coordinates using the correct industry-standard flow:

  • First call: Shows the native OS permission dialog (on both iOS and Android).
  • User taps "Allow": Fetches GPS signal and returns { status: 'granted', latitude: number, longitude: number, accuracy: number, timestamp: number, altitude: number }.
  • User taps "Deny": Returns { status: 'denied', canAskAgain: true }. The user's choice is respected — no forced redirect.
  • Subsequent call (after permanent denial): Returns { status: 'denied', canAskAgain: false }. If you pass showAlertConfig: true, it will automatically show a native alert explaining why the permission is needed and provide an "Open Settings" button that redirects the user directly to your app's Location settings.
  • Location Services Off: If global location services (GPS) are disabled, it automatically prompts the user natively to turn them on (via Google Play Services on Android). If they tap "No thanks", or if you're on a device without Play Services, it aborts and returns { status: 'denied', error: 'LOCATION_SERVICES_DISABLED' }. This gives you full control to show a custom explanation dialog before manually calling PermissionKit.openLocationSettings().

Options:

  • timeout: GPS fetch timeout in ms (default 10000ms).
  • accuracy: Location accuracy level (default 'balanced'):
    • 'high': GPS-level precision (~5m). Slower, uses more battery.
    • 'balanced': Wi-Fi/Cell tower precision (~100m). Fast, battery-friendly.
    • 'low': City-level precision (~1km). Fastest, minimal battery.
  • fetchCoordinates: If false, acts as a "permission only" request without turning on the GPS hardware (default true).
  • showAlertConfig: If true, shows a native alert to navigate to Settings when permanently denied.
  • showErrorAlerts: If true, automatically shows native UI messages (Toast on Android, Alert on iOS) for common location errors like timeout or services disabled (default true).
  • errorMessages: Optional object to override the default error messages shown by showErrorAlerts.

Return Type:

type LocationResult =
  | { status: 'granted'; latitude: number; longitude: number; accuracy: number; altitude: number; timestamp: number }
  | { status: 'denied'; canAskAgain?: boolean; error?: 'LOCATION_SERVICES_DISABLED' | 'TIMEOUT' | 'LOCATION_UNAVAILABLE' }
  | { status: 'restricted' }
  | { status: 'unavailable' };
const result = await PermissionKit.location({
  timeoutMs: 15000,
  fetchCoordinates: true,
  showAlertConfig: true,
  alertConfig: {
    title: 'Location Required',
    description: 'We need your location to show nearby restaurants.'
  }
});

if (result.status === 'granted') {
  console.log(`Lat: ${result.latitude}, Lng: ${result.longitude}`);
} else if (result.status === 'denied') {
  if (result.error === 'LOCATION_SERVICES_DISABLED') {
    // Show your custom dialog: "Please enable GPS to use this feature"
  } else {
    // User denied app permission
  }
} else if (result.status === 'restricted') {
  // Parental controls or MDM blocking location (iOS)
}

PermissionKit.checkLocation()

Check the current location permission status without showing any dialog or fetching coordinates.

const result = await PermissionKit.checkLocation();
// { status: 'granted' | 'denied' | 'restricted', canAskAgain: boolean }

Platform Support

| Feature | Android | iOS | |-----------------------|---------|-----| | Battery Optimization | ✅ | ⚠️ unavailable | | Overlay Permission | ✅ | ⚠️ unavailable | | Usage Access | ✅ | ⚠️ unavailable | | Exact Alarm | ✅ | ⚠️ unavailable | | Full Screen Intent | ✅ | ⚠️ unavailable | | Accessibility Service | ✅ | ⚠️ unavailable | | Do Not Disturb Access | ✅ | ⚠️ unavailable | | Notifications | ✅ | ✅ | | Location | ✅ | ✅ | | Media | ✅ | ✅ |

iOS Note: Battery Optimization, Overlay, Usage Access, Exact Alarm, Full Screen Intent, Accessibility Service, and DND Access are Android-only concepts. Calling them on iOS immediately returns { status: 'unavailable' } without showing any UI. Notifications, Location, and Media are natively supported on both platforms.

Simulator Note: When testing on the iOS Simulator, calling openSettings() or pressing the "Open Settings" button in the permission alert may open the Settings App's home screen instead of the specific app's settings page. This is a known iOS Simulator bug. Testing on a real physical device will correctly open the specific app's settings.


Roadmap

  • [x] Battery Optimization (Android)
  • [x] Overlay Permission (Android)
  • [x] Exact Alarm (Android)
  • [x] Full Screen Intent (Android)
  • [x] Accessibility Service (Android)
  • [x] Do Not Disturb Access (Android)
  • [x] Notifications (Android + iOS)
  • [x] Location (Android + iOS)
  • [x] Media (Android + iOS)
  • [x] Expo Config Plugin
  • [x] Usage Access (Android)
  • [ ] Write System Settings (Android)
  • [ ] ensure() helper

Issues & Feedback

If you encounter any bugs, have feature requests, or want to contribute to the library, please feel free to open an issue or pull request on our GitHub repository:

https://github.com/AbrarMehraj/plugins-playground/issues

We highly value developer feedback to make this the best permissions library in the React Native ecosystem!


License

MIT