Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ class CameraDevicesManager(private val reactContext: ReactApplicationContext) :

override fun getName(): String = TAG

override fun initialize() {
super.initialize()
cameraManager.registerAvailabilityCallback(callback, null)
// Init cameraProvider + manager as early as possible
init {
Comment on lines +62 to +63
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!

coroutineScope.launch {
try {
Log.i(TAG, "Initializing ProcessCameraProvider...")
Expand All @@ -76,6 +75,12 @@ class CameraDevicesManager(private val reactContext: ReactApplicationContext) :
}
}

// Note: initialize() will be called after getConstants on new arch!
override fun initialize() {
super.initialize()
cameraManager.registerAvailabilityCallback(callback, null)
}

override fun invalidate() {
cameraManager.unregisterAvailabilityCallback(callback)
super.invalidate()
Expand Down