Our current solution
Breaking down the problem
So, we have a few necessities:
1. PermissionManager
Since the only required actions for dealing with permissions at a low level are requesting and checking it, and both return the new status of the permission, we only really need to worry about tracking the permission states in a globally accessible way. The PermissionManager
class does exactly that. It's in the common
package simply because it's functionality is general enough to be reutilized in a non React Native environment such as the browser should the need arise.
import { PermissionManager } from '@codeleap/common'import RNPermissions from 'react-native-permissions'import { Platform } from 'react-native'const platform = Platform.OSexport const AppPermissions = new PermissionManager({camera: {onAsk: async () => { // Called when we actually want the OS to ask the permission with the dialogconst status = await RNPermissions.request(RNPermissions.PERMISSIONS[platform.toUpperCase()].CAMERA)return status // This is a string which can be one of many statuses, check the react-native-permissions docs for more info on it},onCheck: async () => { // Called when we just need to get the latest stateconst status = await RNPermissions.check(RNPermissions.PERMISSIONS[platform.toUpperCase()].CAMERA)return status},},notifications: {onAsk: async () => {const { status } = await RNPermissions.requestNotifications(['alert', 'badge', 'sound'])return status},onCheck: async () => {return (await RNPermissions.checkNotifications()).status},},})
Most useful methods are:
AppPermissions.get('camera', {ask: true // If you just want to get the latest state, set it to false}).then(({isGranted}) => {if(isGranted){// do something}})AppPermissions.values // Gets the current state, includes all permissionsAppPermissions.update() // Check every permission to update it's state// The callback will fire whenever the state for any permission changesconst unsubscribe = AppPermissions.onChange((name, newState) => {console.log(name, newState.status) // 'camera granted' if the camera permission was granted})unsubscribe() // Removes the listener for onChange
2. Configuring the display
This is just a matter of explicitly setting the data show on the screen when requesting permissions, and some of the behavior it should have when pressing buttons.
import { PermissionManager } from '@codeleap/common'import RNPermissions from 'react-native-permissions'import { Platform } from 'react-native'import { Permissions } from '@codeleap/mobile'const platform = Platform.OSexport const AppPermissions = new PermissionManager({// ...})export type PermissionNames = keyof typeof AppPermissions.valuesexport const permissionNames = Object.keys(AppPermissions.values) as PermissionNames[]const modalConfig:Permissions.PermissionModalsConfig<keyof typeof AppPermissions.values> = {camera: { // Properties at the highest level are treated as default, but can be overriden per permission statusonAllow: 'ask', // Will call AppPermissions.get('camera', { ask: true })title: 'Camera',icon: 'image',description: [`${AppName} needs access to your camera to capture and share photos.`,],blocked: {description: [`${AppName} needs access to your camera to capture and share photos.`,'Please open settings and allow access to "Camera"',],onAllow: 'openSettings', // Will navigate to de device settings},},notifications: {icon: 'notifications',description: [`Notifications may include alerts, sounds, and icon badges.`,`These can be configured later in the Settings app.`,],title: `${AppName} would like to send you notifications`,onAllow: 'ask',blocked: {onAllow: 'openSettings',},}}
3. Connecting to the app
The @codeleap/mobile
package includes a Permissions
module, which can be used to wrap your app with a <Permissions.Provider/>
, exposing data and two utilities for easier permission management. Do keep in mind that it depends on the ModalManager
module, and as such, needs to be rendered inside <ModalManager.Provider />
.
// services/permissions.tsximport { PermissionManager } from '@codeleap/common'import RNPermissions from 'react-native-permissions'import { Platform } from 'react-native'import { Permissions } from '@codeleap/mobile'const platform = Platform.OSexport const AppPermissions = new PermissionManager({// ...})export type PermissionNames = keyof typeof AppPermissions.valuesexport const permissionNames = Object.keys(AppPermissions.values) as PermissionNames[]const modalConfig:Permissions.PermissionModalsConfig<keyof typeof AppPermissions.values> = {// ...}export const {// We'll hook this up to the modal laterusePermissionModal,// This exposes handlers for requesting permissions and accessing their stateusePermissions,} = Permissions.createTypedPermissionHooks({modalConfig,permissionsManager: AppPermissions,})const Provider = ({ children }) => <Permissions.Provider AppPermissions={AppPermissions} modalConfig={modalConfig}>{children}</Permissions.Provider>export {Provider as PermissionsProvider,}
Wrap your app with <PermissionsProvider />
.
4. Modal and requesting permissions
import {usePermissionModal,permissionNames} from '@/services/permissions'export function PermissionModal(props: PermissionModalProps) {const { permissionName } = propsconst {config,onAllow,onDeny,modalId,} = usePermissionModal(permissionName)return <ModalManager.ModaldebugName={`Permission ${permissionName} modal`}absolute={false}id={modalId}closable={false}dismissOnBackdrop={false}>{ /* View for your modal goes here */ }</ModalManager.Modal>}export function AllPermissionModals() { // This should be rendered somewhere in the appreturn <>{permissionNames.map(permissionName => <PermissionModal key={permissionName} permissionName={permissionName}/>)}</>}
To request permissions when pressing a button
import {usePermissions} from '@/services/permissions'function Component(){const permissions = usePermissions()return <Button onPress={() => {permissions.askPermission('camera' , (status) => {if(status === 'granted'){// handle opening camera}})permissions.askMany(['camera','library'], (result) => {if(result.overall === 'granted'){// handle success for both permissions granted}})}} />}