Skip to content

Conversation

@hannojg
Copy link
Contributor

@hannojg hannojg commented Jul 14, 2025

What

I was looking at a bug where no camera devices were returned, however, getting them later I was getting values:

CleanShot_2025-07-14_at_11 53 482x CleanShot_2025-07-14_at_11 53 45

This resulted in the camera never displaying

Changes

Problem:

  • Before we init the cameraProvider in onInitialize()
  • In our app code however we request the camera devices through the native module's constants
  • getConstants() will be called before onInitialize() so we just return an empty camera list
  • As the camera devices never changed no new event would be emitted that would refresh the devices list

Solution:

  • Move the cameraProvider + manager to the init which will be called earlier, react context should be present at this point as its a constructor arg
  • This way when getConstants gets called we can get the devices, which fixes the. problem
  • I kept cameraManager.registerAvailabilityCallback in onInitialize() as this needs to the looper, which we don't have available in init (at least on new arch as its init from a bg thread)

Note: I am wondering if this is a performance improvement as well, as before I assume something as:

  • getCameraDevices: []
  • event emitted which camera devices changed emitted
  • getCameraDevices: [...]

So first frame time might be faster? 🤷

Tested on

  • Samsung S23

Related issues

Not sure, haven't checked

@vercel
Copy link

vercel bot commented Jul 14, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-native-vision-camera ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 10:19am

@mrousavy
Copy link
Owner

ah dope!

@mrousavy mrousavy merged commit 5206c78 into main Jul 14, 2025
5 checks passed
@mrousavy mrousavy deleted the fix/new-arch-no-camera-devices branch July 14, 2025 10:23
Comment on lines +62 to +63
// Init cameraProvider + manager as early as possible
init {
Copy link
Contributor

@n-kulic n-kulic Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hannojg @mrousavy Just noting that in this PR, sendAvailableDevicesChangedEvent() was indirectly moved into init, which is causing crashes on hot restart because js bridge isn't ready yet. We opened a follow-up PR to fix this by moving event back to initialize():

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uhm - does that not effectively just revert our changes? @hannojg can you review/confirm?

Copy link
Contributor

@n-kulic n-kulic Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrousavy Not a full revert, early init of native part (cameraProvider/extensionsManager) is still kept in init.

The only change is moving sendAvailableDevicesChangedEvent() back into initialize(), because that's where it's safe to call getJSModule.

The problem with previous approach is that getJSModule was being called from init (by sendAvailableDevicesChangedEvent) before JS bridge was ready. You can see that here:

  private ReactApplicationContext createReactContext(...) {
    ...
    // CameraDevicesManager.init triggers here
    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages);

   // JS bridge initialization starts here
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
    ...
    
    // Key point: calling getJSModule before this will throw IllegalStateException,
    // which is what currently happens in CameraDevicesManager.init.sendAvailableDevicesChangedEvent.getJSModule
    reactContext.initializeWithInstance(catalystInstance);

So why it works on cold start?

Short answer: timing/race condition.

Long answer: cameraProvider and extensionsManger initialize asynchronously by coroutine. On cold start coroutine usually schedules slightly later, so JS bridge is ready by the time sendAvailableDevicesChangedEvent() runs. Hot restart exposes this timing issue.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it thanks!

zehra-tok pushed a commit to tos-analytics/react-native-vision-camera that referenced this pull request Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants