From 591cf30a06b00f560c0489085c1ad7585c7582d5 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 19 Dec 2023 14:22:04 +0100 Subject: [PATCH] perf: Make `getCameraPermission` and `getMicrophonePermission` synchronous (#2302) --- docs/docs/guides/SETUP.mdx | 4 ++-- .../com/mrousavy/camera/CameraViewModule.kt | 12 +++++------ package/example/src/App.tsx | 18 ++++------------- package/example/src/CameraPage.tsx | 6 +----- package/ios/CameraViewManager.m | 4 ++-- package/ios/CameraViewManager.swift | 16 ++++++--------- package/src/Camera.tsx | 20 ++++--------------- package/src/hooks/useCameraPermission.ts | 14 +++---------- 8 files changed, 28 insertions(+), 66 deletions(-) diff --git a/docs/docs/guides/SETUP.mdx b/docs/docs/guides/SETUP.mdx index 2f1bd49..d134ed8 100644 --- a/docs/docs/guides/SETUP.mdx +++ b/docs/docs/guides/SETUP.mdx @@ -164,8 +164,8 @@ There could be three states to this: Simply use the **get** functions to find out if a user has granted or denied permission before: ```ts -const cameraPermission = await Camera.getCameraPermissionStatus() -const microphonePermission = await Camera.getMicrophonePermissionStatus() +const cameraPermission = Camera.getCameraPermissionStatus() +const microphonePermission = Camera.getMicrophonePermissionStatus() ``` A permission status can have the following values: diff --git a/package/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/package/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt index a6b63cb..532359f 100644 --- a/package/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt +++ b/package/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt @@ -149,24 +149,24 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase return activity?.shouldShowRequestPermissionRationale(permission) ?: false } - @ReactMethod - fun getCameraPermissionStatus(promise: Promise) { + @ReactMethod(isBlockingSynchronousMethod = true) + fun getCameraPermissionStatus(): String { val status = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.CAMERA) var parsed = PermissionStatus.fromPermissionStatus(status) if (parsed == PermissionStatus.DENIED && canRequestPermission(Manifest.permission.CAMERA)) { parsed = PermissionStatus.NOT_DETERMINED } - promise.resolve(parsed.unionValue) + return parsed.unionValue } - @ReactMethod - fun getMicrophonePermissionStatus(promise: Promise) { + @ReactMethod(isBlockingSynchronousMethod = true) + fun getMicrophonePermissionStatus(): String { val status = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.RECORD_AUDIO) var parsed = PermissionStatus.fromPermissionStatus(status) if (parsed == PermissionStatus.DENIED && canRequestPermission(Manifest.permission.RECORD_AUDIO)) { parsed = PermissionStatus.NOT_DETERMINED } - promise.resolve(parsed.unionValue) + return parsed.unionValue } @ReactMethod diff --git a/package/example/src/App.tsx b/package/example/src/App.tsx index a0e8eb9..8c9d239 100644 --- a/package/example/src/App.tsx +++ b/package/example/src/App.tsx @@ -1,12 +1,12 @@ import { NavigationContainer } from '@react-navigation/native' -import React, { useEffect, useState } from 'react' +import React from 'react' import { createNativeStackNavigator } from '@react-navigation/native-stack' import { PermissionsPage } from './PermissionsPage' import { MediaPage } from './MediaPage' import { CameraPage } from './CameraPage' import { CodeScannerPage } from './CodeScannerPage' import type { Routes } from './Routes' -import { Camera, CameraPermissionStatus } from 'react-native-vision-camera' +import { Camera } from 'react-native-vision-camera' import { GestureHandlerRootView } from 'react-native-gesture-handler' import { StyleSheet } from 'react-native' import { DevicesPage } from './DevicesPage' @@ -14,21 +14,11 @@ import { DevicesPage } from './DevicesPage' const Stack = createNativeStackNavigator() export function App(): React.ReactElement | null { - const [cameraPermission, setCameraPermission] = useState() - const [microphonePermission, setMicrophonePermission] = useState() - - useEffect(() => { - Camera.getCameraPermissionStatus().then(setCameraPermission) - Camera.getMicrophonePermissionStatus().then(setMicrophonePermission) - }, []) + const cameraPermission = Camera.getCameraPermissionStatus() + const microphonePermission = Camera.getMicrophonePermissionStatus() console.log(`Re-rendering Navigator. Camera: ${cameraPermission} | Microphone: ${microphonePermission}`) - if (cameraPermission == null || microphonePermission == null) { - // still loading - return null - } - const showPermissionsPage = cameraPermission !== 'granted' || microphonePermission === 'not-determined' return ( diff --git a/package/example/src/CameraPage.tsx b/package/example/src/CameraPage.tsx index b976586..c928b2b 100644 --- a/package/example/src/CameraPage.tsx +++ b/package/example/src/CameraPage.tsx @@ -31,7 +31,7 @@ type Props = NativeStackScreenProps export function CameraPage({ navigation }: Props): React.ReactElement { const camera = useRef(null) const [isCameraInitialized, setIsCameraInitialized] = useState(false) - const [hasMicrophonePermission, setHasMicrophonePermission] = useState(false) + const hasMicrophonePermission = useMemo(() => Camera.getMicrophonePermissionStatus() === 'granted', []) const zoom = useSharedValue(0) const isPressingButton = useSharedValue(false) @@ -131,10 +131,6 @@ export function CameraPage({ navigation }: Props): React.ReactElement { // Run everytime the neutralZoomScaled value changes. (reset zoom when device changes) zoom.value = neutralZoom }, [neutralZoom, zoom]) - - useEffect(() => { - Camera.getMicrophonePermissionStatus().then((status) => setHasMicrophonePermission(status === 'granted')) - }, []) //#endregion //#region Pinch to Zoom Gesture diff --git a/package/ios/CameraViewManager.m b/package/ios/CameraViewManager.m index d11c0a8..fa2e9ca 100644 --- a/package/ios/CameraViewManager.m +++ b/package/ios/CameraViewManager.m @@ -14,8 +14,8 @@ @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManager) // Module Functions -RCT_EXTERN_METHOD(getCameraPermissionStatus : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); -RCT_EXTERN_METHOD(getMicrophonePermissionStatus : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); +RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getCameraPermissionStatus); +RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getMicrophonePermissionStatus); RCT_EXTERN_METHOD(requestCameraPermission : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(requestMicrophonePermission : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); diff --git a/package/ios/CameraViewManager.swift b/package/ios/CameraViewManager.swift index 2739a0e..437f325 100644 --- a/package/ios/CameraViewManager.swift +++ b/package/ios/CameraViewManager.swift @@ -84,19 +84,15 @@ final class CameraViewManager: RCTViewManager { } @objc - final func getCameraPermissionStatus(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - withPromise(resolve: resolve, reject: reject) { - let status = AVCaptureDevice.authorizationStatus(for: .video) - return status.descriptor - } + final func getCameraPermissionStatus() -> String { + let status = AVCaptureDevice.authorizationStatus(for: .video) + return status.descriptor } @objc - final func getMicrophonePermissionStatus(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - withPromise(resolve: resolve, reject: reject) { - let status = AVCaptureDevice.authorizationStatus(for: .audio) - return status.descriptor - } + final func getMicrophonePermissionStatus() -> String { + let status = AVCaptureDevice.authorizationStatus(for: .audio) + return status.descriptor } @objc diff --git a/package/src/Camera.tsx b/package/src/Camera.tsx index 6f8864c..5e18296 100644 --- a/package/src/Camera.tsx +++ b/package/src/Camera.tsx @@ -345,30 +345,18 @@ export class Camera extends React.PureComponent { * the user has permitted the app to use the camera. * * To actually prompt the user for camera permission, use {@linkcode Camera.requestCameraPermission | requestCameraPermission()}. - * - * @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting the current permission status. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error */ - public static async getCameraPermissionStatus(): Promise { - try { - return await CameraModule.getCameraPermissionStatus() - } catch (e) { - throw tryParseNativeCameraError(e) - } + public static getCameraPermissionStatus(): CameraPermissionStatus { + return CameraModule.getCameraPermissionStatus() } /** * Gets the current Microphone-Recording Permission Status. Check this before mounting the Camera to ensure * the user has permitted the app to use the microphone. * * To actually prompt the user for microphone permission, use {@linkcode Camera.requestMicrophonePermission | requestMicrophonePermission()}. - * - * @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting the current permission status. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error */ - public static async getMicrophonePermissionStatus(): Promise { - try { - return await CameraModule.getMicrophonePermissionStatus() - } catch (e) { - throw tryParseNativeCameraError(e) - } + public static getMicrophonePermissionStatus(): CameraPermissionStatus { + return CameraModule.getMicrophonePermissionStatus() } /** * Shows a "request permission" alert to the user, and resolves with the new camera permission status. diff --git a/package/src/hooks/useCameraPermission.ts b/package/src/hooks/useCameraPermission.ts index f8cb268..6309bc5 100644 --- a/package/src/hooks/useCameraPermission.ts +++ b/package/src/hooks/useCameraPermission.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useState } from 'react' import { Camera } from '../Camera' interface PermissionState { @@ -31,7 +31,7 @@ interface PermissionState { * ``` */ export function useCameraPermission(): PermissionState { - const [hasPermission, setHasPermission] = useState(false) + const [hasPermission, setHasPermission] = useState(() => Camera.getCameraPermissionStatus() === 'granted') const requestPermission = useCallback(async () => { const result = await Camera.requestCameraPermission() @@ -40,10 +40,6 @@ export function useCameraPermission(): PermissionState { return hasPermissionNow }, []) - useEffect(() => { - Camera.getCameraPermissionStatus().then((s) => setHasPermission(s === 'granted')) - }, []) - return { hasPermission, requestPermission, @@ -65,7 +61,7 @@ export function useCameraPermission(): PermissionState { * ``` */ export function useMicrophonePermission(): PermissionState { - const [hasPermission, setHasPermission] = useState(false) + const [hasPermission, setHasPermission] = useState(() => Camera.getMicrophonePermissionStatus() === 'granted') const requestPermission = useCallback(async () => { const result = await Camera.requestMicrophonePermission() @@ -74,10 +70,6 @@ export function useMicrophonePermission(): PermissionState { return hasPermissionNow }, []) - useEffect(() => { - Camera.getMicrophonePermissionStatus().then((s) => setHasPermission(s === 'granted')) - }, []) - return { hasPermission, requestPermission,