react-native-vision-camera: ๐Ÿ› Unmounting the Camera component does not turn off the torch

What were you trying to do?

Hi,

I am currently using vision-camera in a project to read QR Codes (using โ€˜vision-camera-code-scannerโ€™ for the FrameProcessor). The project also uses react-navigation.

The user can open a page dedicated to QR Code scanning, which only has the active Camera and a button to toggle the torch. If the torch is kept on and the user goes back (popping the page out of the navigation current stack), the torch stays on.

The only way to turn it off is then to reopen the page, or minimize the whole application.

I did not find any imperative API to use with a ref either.

Reproduceable Code

import React from 'react';

import {
  TouchableOpacity,
} from 'react-native';

import {Camera, useCameraDevices} from 'react-native-vision-camera';

export default function QrPageExemple(props) {
  const [cameraPermission, setCameraPermission] = React.useState("");
  const [enableFlashlight, setEnableFlashLight] = React.useState(false);
  const devices = useCameraDevices();
  const device = devices.back;

  React.useEffect(() => {
    async function requestPermission() {
      const status = await Camera.requestCameraPermission();
      setCameraPermission(status);
    }

    requestPermission();

    // Cleaning, doesn't work
    return () => {
      setEnableFlashLight(false);
    };
  }, []);

  return (
    <>
      <Camera device={device} torch={enableFlashlight ? "on" : "off"} />
      <TouchableOpacity
        onPress={() => {
          setEnableFlashLight(!enableFlashlight);
        }}
      ></TouchableOpacity>
    </>
  );
}

What happened instead?

The torch stayed on after the Camera component was unmounted. Using a cleaning useEffect() hook to set the torch prop to false has no effect.

Relevant log output

No response

Device

Samsung Galaxy A51 (Android 12), untested on iOS

VisionCamera Version

2.13.2

Additional information

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 39 (15 by maintainers)

Commits related to this issue

Most upvoted comments

๐ŸŽ‰๐ŸŽ‰Hi Guys, I have workaround that android vision-camera make allow torch control by ref๐ŸŽ‰๐ŸŽ‰

Allow controlling torch by ref in android

diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
new file mode 100644
index 0000000..4057c33
--- /dev/null
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
@@ -0,0 +1,9 @@
+package com.mrousavy.camera
+
+import kotlinx.coroutines.guava.await
+
+suspend fun CameraView.enableTorch(status: Boolean){
+    val cameraControl = camera?.cameraControl ?: throw CameraNotReadyError()
+
+    cameraControl.enableTorch(status)
+}
\ No newline at end of file
diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
index 7672b15..e5e1a89 100644
--- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
@@ -400,4 +400,15 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
       promise.reject("NO_ACTIVITY", "No PermissionAwareActivity was found! Make sure the app has launched before calling this function.")
     }
   }
+  
+  @ReactMethod
+  fun enableTorch(viewTag: Int,status:Boolean, promise: Promise){
+    coroutineScope.launch{
+      withPromise(promise){
+        val view = findCameraView(viewTag)
+        view.enableTorch(status)
+        return@withPromise null
+      }
+    }
+  }
 }
diff --git a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h
index a2ccdcb..597691c 100644
--- a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h	
+++ b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h	
@@ -53,7 +53,7 @@ objc_name : NSObject<FrameProcessorPluginBase>
 @end                                                                                \
 @implementation objc_name (FrameProcessorPlugin)                                    \
                                                                                     \
-__attribute__((constructor)) static void VISION_CONCAT(initialize_, objc_name)()    \
++(void)load  \
 {                                                                                   \
   [FrameProcessorPluginRegistry addFrameProcessorPlugin:@"__" @ #name callback:^id(Frame* frame, NSArray<id>* args) {    \
     return [objc_name callback:frame withArgs:args];                               \
diff --git a/node_modules/react-native-vision-camera/src/Camera.tsx b/node_modules/react-native-vision-camera/src/Camera.tsx
index 68417ac..fbfeb64 100644
--- a/node_modules/react-native-vision-camera/src/Camera.tsx
+++ b/node_modules/react-native-vision-camera/src/Camera.tsx
@@ -293,6 +293,19 @@ export class Camera extends React.PureComponent<CameraProps> {
       throw tryParseNativeCameraError(e);
     }
   }
+
+  /**
+   * Control the cameras torch by given status.
+   * @param {Boolean} status
+   */
+  public async enableTorch(status: Boolean): Promise<void> {
+    try{
+      return await CameraModule.enableTorch(this.handle,status)
+    } catch (e){
+      throw tryParseNativeCameraError(e)
+    }
+  }
+
   //#endregion
 
   /**

Usage

   useEffect(() => {
        const listener = BackHandler.addEventListener('hardwareBackPress', function () {
            ref.current.enableTorch(false);
            return false;
        });
        return () => {
            listener.remove();
        };
    }, []);

you can get react-native-vision-camera+2.15.1.patch file at the link

how to use .patch file patch-package

Working On

  • typescript method auto complete
  • supporting ios

After finish these jobs i will make a PR, until now you can fix your bug with this patch!! See ya ๐ŸŽ‰

Nice work! Seems good to me.

I will work on a fix soon

@markoprodanovic i will notice you by a comment that you can get rid of patch files ๐Ÿ˜ƒ

I have found the issue: updateLifecycleState is called from onDetachedFromWindow and it assumes the hostLifecycleState is the indicator to update the Camera. However this is only bound to the actual current running host session. Removing the view from the tree will not cause it to be marked as โ€œDESTROYEDโ€. If I however force the lifecycle state to be set to DESTROYED from onDetachFromWindow the Camera will be released appropriately and the torch be turned off. I am not sure how this works for anyone correctly right now, but to me it looks like a general issue. @mrousavy I would be happy to open a PR if you agree with me

Iโ€™m experiencing the same problem on v3 on iOS

@whtjs it was a mistake on my end and a misunderstanding of how to use patch-package. For context, I was overwriting the patch file by running my own yarn patch-package react-native-vision-camera. I was also working with v0.5.2 instead of v0.5.1. Once I sorted out those things it worked great! Thank you ๐Ÿ˜ƒ

Would be great to get this in an official release

๐ŸŽ‰๐ŸŽ‰Hi Guys, I have workaround that android vision-camera make allow torch control by ref๐ŸŽ‰๐ŸŽ‰

Allow controlling torch by ref in android

diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
new file mode 100644
index 0000000..4057c33
--- /dev/null
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
@@ -0,0 +1,9 @@
+package com.mrousavy.camera
+
+import kotlinx.coroutines.guava.await
+
+suspend fun CameraView.enableTorch(status: Boolean){
+    val cameraControl = camera?.cameraControl ?: throw CameraNotReadyError()
+
+    cameraControl.enableTorch(status)
+}
\ No newline at end of file
diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
index 7672b15..e5e1a89 100644
--- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
@@ -400,4 +400,15 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
       promise.reject("NO_ACTIVITY", "No PermissionAwareActivity was found! Make sure the app has launched before calling this function.")
     }
   }
+  
+  @ReactMethod
+  fun enableTorch(viewTag: Int,status:Boolean, promise: Promise){
+    coroutineScope.launch{
+      withPromise(promise){
+        val view = findCameraView(viewTag)
+        view.enableTorch(status)
+        return@withPromise null
+      }
+    }
+  }
 }
diff --git a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h
index a2ccdcb..597691c 100644
--- a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h	
+++ b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h	
@@ -53,7 +53,7 @@ objc_name : NSObject<FrameProcessorPluginBase>
 @end                                                                                \
 @implementation objc_name (FrameProcessorPlugin)                                    \
                                                                                     \
-__attribute__((constructor)) static void VISION_CONCAT(initialize_, objc_name)()    \
++(void)load  \
 {                                                                                   \
   [FrameProcessorPluginRegistry addFrameProcessorPlugin:@"__" @ #name callback:^id(Frame* frame, NSArray<id>* args) {    \
     return [objc_name callback:frame withArgs:args];                               \
diff --git a/node_modules/react-native-vision-camera/src/Camera.tsx b/node_modules/react-native-vision-camera/src/Camera.tsx
index 68417ac..fbfeb64 100644
--- a/node_modules/react-native-vision-camera/src/Camera.tsx
+++ b/node_modules/react-native-vision-camera/src/Camera.tsx
@@ -293,6 +293,19 @@ export class Camera extends React.PureComponent<CameraProps> {
       throw tryParseNativeCameraError(e);
     }
   }
+
+  /**
+   * Control the cameras torch by given status.
+   * @param {Boolean} status
+   */
+  public async enableTorch(status: Boolean): Promise<void> {
+    try{
+      return await CameraModule.enableTorch(this.handle,status)
+    } catch (e){
+      throw tryParseNativeCameraError(e)
+    }
+  }
+
   //#endregion
 
   /**

Usage

   useEffect(() => {
        const listener = BackHandler.addEventListener('hardwareBackPress', function () {
            ref.current.enableTorch(false);
            return false;
        });
        return () => {
            listener.remove();
        };
    }, []);

you can get react-native-vision-camera+2.15.1.patch file at the link

how to use .patch file patch-package

Working On

  • typescript method auto complete
  • supporting ios

After finish these jobs i will make a PR, until now you can fix your bug with this patch!! See ya ๐ŸŽ‰

Thank so much, it worked for me, but first I had to patch the patch-package ๐Ÿ˜„ because I had a buffer bug https://github.com/ds300/patch-package/issues/166

I used this patch https://gist.github.com/brussee/e382ed12ca007a88170289e54b526063 but with maxBuffer: 1024 * 1024 * 1000,

Nice work! Seems good to me.

I will work on a fix soon

Thank you always for your dedication. Hope i can help you!

I have found the issue: updateLifecycleState is called from onDetachedFromWindow and it assumes the hostLifecycleState is the indicator to update the Camera. However this is only bound to the actual current running host session. Removing the view from the tree will not cause it to be marked as โ€œDESTROYEDโ€. If I however force the lifecycle state to be set to DESTROYED from onDetachFromWindow the Camera will be released appropriately and the torch be turned off. I am not sure how this works for anyone correctly right now, but to me it looks like a general issue. @mrousavy I would be happy to open a PR if you agree with me

It was helped me=) Look like (in CameraView.kt):

override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    updateLifecycleState()
    lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
  }