Permissions
Nearly every Expo module that needs a permission exposes the same shape: a getXPermissionsAsync,
a requestXPermissionsAsync, and a useXPermissions() hook that is those two plus React state.
Permission rebuilds that hook as signals, for any module's pair of functions - it is not a facade
per module, so the module stays your app's own dependency, and it is what
ImagePicker, Location and
Camera are built on.
import { Permission } from '@ng-native/expo';
Permission lives on the bare @ng-native/expo import, not behind an entry point - it is not
bound to one optional module.
Install
Nothing extra: Permission needs no Expo module of its own. Use it against whichever module's
permission functions you are calling.
import { Permission } from '@ng-native/expo';The smallest thing that works
import * as Camera from 'expo-camera';
import { Component, inject } from '@angular/core';
import { Text } from '@ng-native/components';
import { Permission } from '@ng-native/expo';
@Component({
selector: 'app-scanner',
imports: [Text],
template: '<text>{{ camera.status() }}</text>',
})
export class Scanner {
protected readonly camera = Permission.of(
Camera.Camera.getCameraPermissionsAsync,
Camera.Camera.requestCameraPermissionsAsync,
);
protected async scan(): Promise<void> {
if (await this.camera.ensure()) this.startScanning();
}
private startScanning(): void {}
}Building one
Permission.of(get, request) takes a module's own pair of functions and returns a
Permission. Nothing is asked until you call one of the methods below - constructing it does not
touch the platform.
readonly notifications = Permission.of(getPermissionsAsync, requestPermissionsAsync);Reading and asking
statusis a signal:'granted','denied'or'undetermined'- or'unknown'before anything has asked the platform, which is not the same as'undetermined'.'undetermined'means the platform has been asked and the person has not decided;'unknown'means nobody has asked yet.grantedis a signal: whether the permission is currently granted.blockedis a signal: true once the platform will no longer show a dialog at all - refused for good. This is the moment to send the person to Settings instead of asking again, withDeepLinks'openSettings(). It is not final: see Coming back from Settings.check()asks the platform what it currently thinks, without showing the person anything.request()shows the dialog and resolves to whether they granted it.ensure()is the method most call sites want: it has the permission if it can be had, and only shows a dialog if showing one would do something. A granted answer is kept, so it returnstruewithout asking the platform again. Anything else, including nothing asked yet, is checked first, which shows nothing. Then it returnstrueif granted,falseif blocked (asking again would resolve to the same no while reading as though the person had been consulted twice), and otherwise shows the request dialog.
protected async open(): Promise<void> {
if (await this.camera.ensure()) this.scanning.set(true);
}Coming back from Settings
A permission refused for good can still be turned on in Settings, and neither platform restarts the app for that. So a no is never taken as final:
ensure()checks a no again before answering. The next tap after the person comes back works, with no dialog.- A
Permissionbuilt in an injection context (a field of a service or a component, as above) also checks a no again each time the app comes back to the front, soblocked,grantedandstatuschange by themselves and a "turn it on in Settings" screen can go away. It stops when that service or component is destroyed. One built outside an injection context only changes when you callcheck(),request()orensure().
A granted answer is not checked again either way, which keeps the common path free of platform
calls. Call check() where you need to know a permission was not turned off since.
Reference
Permissionclass@ng-native/expoimport { Permission } from '@ng-native/expo';Signals
statusSignal<PermissionResponse['status'] | 'unknown'>unknown until something has asked the platform, which is not the same as undetermined .
grantedSignal<boolean>blockedSignal<boolean>Refused, and the platform will not show a dialog again. Send them to Settings.
Methods
of(get: () => Promise<PermissionResponse>, request: () => Promise<PermissionResponse>): PermissionFrom a module's own two functions, which is how every one of them spells this.
check(): Promise<boolean>Ask the platform what it currently thinks, without showing anything to the user.
request(): Promise<boolean>Show the dialog. Resolves to what the user said.
ensure(): Promise<boolean>Have the permission if it can be had, asking only if asking would do something.