React Native: Managing App Permissions for iOS

A comprehensive guide to managing permissions in React Native

Ross Bulat
Mar 26 · 12 min read

This article covers permission management in React Native, an important consideration to ensure a seamless experience for the end user. What React Native apps need to do is listen to permission changes as they are made outside the app, and update the UX accordingly when the app is summoned back into the foreground. This is due to the fact that permissions are changed from within the Settings app, and not in the app itself.

What apps can do is request a permission via a one-time dialogue prompt, that the user can either accept or decline. It is after this initial decision that the user must jump into Settings to adjust these permissions. Therefore, what React Native must be able to do is:

  • Request permissions on a per-permission basis. We’ll cover how to enable these permissions within Xcode, and use packages like react-native-permissions to read current permission status and request permissions to be granted.
  • Check permissions when a user jumps back into the app on key screens that require those permissions to be granted, such as the camera permission for taking photos and videos in-app, or real-time location permission for apps like ride hailing apps. These checks can be done with event listeners, the implementation of which we will cover further down. If the permission has indeed been toggled, the UX can then update accordingly.

Because iOS handles permission management in the external Settings app, it is useful to understand how App State works in the context of managing permissions, and the differences between an app being in the foreground vs background. We’ll touch on this further down.

It is also worth noting that notification permissions are slightly more verbose than most others, giving the user more granularity to what can be allowed.

Before diving into integration, let’s cover how permissions are enabled in-app, and toggled thereafter.


Permission Workflows

There are two major workflows to be familiar with in order to implement permission management effectively.

1. When the user uses the app for the first time, permission dialogues need to be displayed at a convenient time to ensure the user will consider granting them. This may be after the user registers an account and first signs in to the app, or at a particular screen where a permission is needed to be granted to use that particular feature.

By planning when this will happen, you will know exactly which screen component will invoke those prompts. Consider the workflow of prompting a notifications permission after a user signs in:

Notifications are relied upon by most apps, and are therefore prompted very early in the user experience. For permissions such as the camera or camera roll on the other hand, it may make more sense to prompt the user for those permissions as they attempt to access those resources.

2. When the user changes permissions from within Settings, then jumps back into the app. This is commonly done when an app becomes too notification-happy, or when location services are being used too much for the comfort of privacy, or draining the device’s battery life — users jump into Settings and revoke previously-granted permissions. When the app is opened in the foreground again, its highly probable UX will need to be updated to reflect the degraded app permissions.

This usage pattern will also only effect certain components and is worth planning in advance. Consider a Reminders feature of an app that relies on the notifications permission to be granted:

Consider the above scenario where the user wants to turn back on a permission. There are in-fact APIs provided by React Native and other packages that allow you to create links to the app’s settings within the Settings app.

This link can be achieved with React Native’s Linking API alone, and can be used within onPress handlers to jump straight into Settings with a tap:

Linking to the app-settings: URL will jump directly into the app’s settings within the Settings app.

We’ll also discover simpler APIs further down that achieve the same thing.

Now we’ve outlined the most common scenarios pertaining to permission management, let’s next jump into implementing some APIs to make these scenarios a reality, and how to prepare Xcode to properly support each permission required for your app.


Configuring React Native Permissions

The package this article opts to use for permission management is react-native-permissions. The package is strongly adopted by the React Native community, currently sitting at a strong 98,000 weekly downloads at the time of writing, with its adoption slowly increasing. It is fair to assume that we can expect continued maintenance of this package going forward, making it a reliable choice for permission management.

react-native-permissions is fully compatible with both iOS and Android.

Installing React Native Permissions with Permission Handlers

Installing the package is a multi step process that requires some configuration on the Xcode side and JavaScript side. Firstly, install the package on the JavaScript side within your React Native project directory:

Like most packages that require linkage with Objective-C / Swift APIs, react-native-permissions is no different. But what may be confusing upon first inspection is that each permission handler needs to be declared separately from within your Podfile.

Your Podfile is a file within your ios/ directory that configures which Cocoapods packages are to be installed on the Xcode side of your project. Cocoapods, like yarn or NPM is a dependency manager, but for Objective-C and Swift packages.

If we check out the official documentation under the iOS section, each permission handler is documented individually for us to copy and paste into our own Podfile. Copy each permission you require into your Podfile, located at ios/Podfile. Here is an example of doing so:

With the required permissions copied, now run pod install:

Amending Xcode Usage Descriptions

The last step required in Xcode is to ensure that “Usage Descriptions” exist for each of the permission handlers installed. “Usage Descriptions” typically appear in the permission prompt when requesting access to that permission.

At this stage, it is wise to not only check if the descriptions exist, but to amend them to swap any placeholder text with a prettier format. Navigate to your Info.plist file, that will be sitting at project-name/Supporting/Info.plist. You may have the Usage Descriptions already, like in the following screenshot:

Usage Descriptions for each permission in Info.plist

If you used Expo to bootstrap your app, each Usage Description will already be sitting in your Info.plist file.

If these do not exist, either add them via the Xcode GUI, or copy and paste the XML provided in the iOS section of the official documentation.

With Xcode configured, we are not ready to integrate permission prompts within React Native.


Requesting Permissions in React Native

Now with react-native-permissions configured on the Xcode side, we can go ahead and import the API into React Native, ready to request and check permissions.

The API itself is rather simple, allowing us to check() permissions and request() permissions. For notifications, there are two other methods, checkNotifications() and requestNotifications() — we’ll touch on these further down. check() and request() are asynchronous and take one argument, being the permission identifier. For added convenience, the package allows us to import these identifiers via the PERMISSIONS object, that contain identifiers for both iOS and Android based permissions:

Take note of your PERMISSIONS of interest — the documentation includes a full list of permissions that derive from this object.

Let’s take these objects and put them to use in a component. The following example checks whether the app has access to the camera after the component renders. A useEffect hook calls an asynchronous handleCameraPermission() method, that updates the local component state with that current permission:

The component firstly assumes that the camera is disabled, before checking the true status once the component renders. The component waits for check() to resolve via async and await. If the default “Denied” status is returned, request() is called to prompt the user to grant the permission. We await this decision, and then update cameraGranted to either true or false, dependant on the returned result of request().

RESULTS object explained

check() and request() actually return a multitude of permission statuses, that may come in useful when carrying out further checks — there are 4 in total:

This feedback is important; it can prevent unnecessary future checks and optimise the user experience if they are all taken into consideration:

  • Before calling request() on any permission, it is good practice to call check() to determine whether the underlying permission feature is available on a particular device. In the case it is not, RESULTS.UNAVAILABLE will be returned. If you app relies on an unavailable permission, it may benefit the end-user to remove all UX pertaining to the permission as to not confuse the user.
  • If a user has not yet responded to a request(), the permission status of the permission in question will be RESULTS.DENIED. This may sound serious, but at this point the app can indeed ask for permission.
  • RESULTS.BLOCKED will return if the app has already requested a permission, to which the user denied access. From here, the only way to grant the permission is through the Settings app. Your app can still react to such changes however, that will be demonstrated further down with event listeners.
  • If a permission is already granted, there will be no need to re-request that permission, nor will the API invoke the dialogue.

The takeaway here is to firstly check() a permission, then request() if that permission is available, then update your UX accordingly. There are efficient ways of fetching permission status that we’ll cover in the next section.

To study permission flow in more detail, refer to the flow diagram for iOS the official documentation lays out — it is very useful for those interested in the differences between the iOS and Android permission flow.


Implementing Permissions

Now with the react-native-permissions APIs understood, this section documents some implementation details. The following notes should be considered for a consistent UX experience.

Using a Splash page to preload permission status

It is common for an app to implement a “splash” screen, to load up app state that is persisted through AsyncStorage, SQLLite or other means. Details such as an already signed in user, app theming, and more are commonly persisted and loaded at the splash screen stage, prepping the app for use.

It is also at this stage that you can load permission statuses through check(), and store the results either in your global state management solution, such as Redux. From here, you can connect this permission state to components and display the correct UX immediately, rather than checking the permission followed by the UX update like we did did earlier.

Now if we take the example code from above, we can set a default value of cameraGranted by referring to global state that was pre-configured at the Splash screen stage:

If you prefer using Redux hooks in React, I have published an article dedicated to integrating them: Redux Hooks in React: An Introduction.

Using openSettings() to jump straight into Settings

We have already covered the Linking method to jump into the Settings app, but react-native-permissions actually provides a helper method to make this slightly easier and more readable, with openSettings().

Simply import and embed where required in your app. The only scenario where you’d want to do this is where a permission has been BLOCKED, to which you can provide some friendly UX to let the user know that they can jump into Settings at any time to grant the permission:

The above component actually returns JSX from a completely different return block than if the permission was granted. This will be the best option if the screen dramatically changes when the permission is either DENIED or BLOCKED.

To further abstract your components, you could create separate components for “denied permission” screens, and render those in place of the original screen in cases where required permissions are not granted.

Permission updates with event listeners

Another key tool for keeping permissions in sync is with event listeners. There are two main instances where event listeners come in useful:

  • During App State changes. When the app is opened back into the foreground, there’s a possibility the user may have toggled a permission back on. React Native’s AppState API allows us to listen to App State changes and react accordingly.
  • During screen changes, where the previous screen may have invoked permission dialogues and therefore updated a permission status. Although not as important as the previous scenario, there may be times where you wish to check permissions when switching screens. For this, react-navigation’s didFocus event listener comes in handy.

To understand more about App State and how to properly integrate event listeners with app state, check out my dedicated article on the topic: Working with App State and Event Listeners in React Native.

Event listeners should be managed through useEffect, where they are removed when the component unmounts. Both AppState and didFocus event listeners can be configured in one useEffect block:

Both event listeners call the same method when the event occurs. handlePermissionCheck can update some local state to reflect whether the permission has changed, as a basis to work with:

This gives your component a means of reacting to permission changes throughout screens and even from external changes from Settings.

What about notification permissions?

checkNotifications() and requestNotifications() work in the same way as check() and request() do, but with an additional settings object supported by them: checkNotifications() returns which settings have been granted along with a status representing the standard RESULTS object, and requestNotifications() hosts one argument that expects an array of settings to be granted.

The settings options are documented here. alert, badge and sound are the main settings you should be targeting when enabling notifications, to give your app the flexibility to display notifications in-line with user expectations.

It is important to note that these APIs only set the permissions for notifications, and do not configure the Objective-C APIs needed to configure push notifications, both remote or local. For more details on this, refer to my article: Apple Push Notifications with React Native and Node.js


In Summary

This article has attempted to provide a comprehensive overview of permission management in React Native, and how to implement them within your projects, taking into consideration common practices to keep UX up to date.

We’ve covered the API of react-native-permissions, and how to integrate them within your components. We also touched on techniques such as preloading permission status within your app’s Splash screen, and storing them in global state. In addition, we covered how event listeners can be used with AppState and screen changes to check for permission updates.

Once your app is granted to send permissions, you may wish to go ahead and implement push notifications, or APNs. To go one step further, follow my integration guide:

Ross Bulat

Written by

Programmer and Author. Director @ JKRBInvestments.com. Creator of ChineseGrammarReview.app for iOS.

More From Medium

More from Ross Bulat

More from Ross Bulat

Also tagged Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade