fix: Fix view-not-found race condition in C++ code (#511)

* Add custom `onViewReady` event to get layout

`componentDidMount` is async, so the native view _might_ not exist yet causing a race condition in the `setFrameProcessor` code.

This PR fixes this by calling `setFrameProcessor` only after the native view has actually mounted, and to ensure that I created a custom event that fires at that point.

* Update CameraView.swift
This commit is contained in:
Marc Rousavy 2021-10-11 18:27:23 +02:00 committed by GitHub
parent 2cf8087ad6
commit 4a73cb96c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 11 deletions

View File

@ -42,6 +42,12 @@ fun CameraView.invokeOnFrameProcessorPerformanceSuggestionAvailable(currentFps:
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraPerformanceSuggestionAvailable", event) reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraPerformanceSuggestionAvailable", event)
} }
fun CameraView.invokeOnViewReady() {
val event = Arguments.createMap()
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraViewReady", event)
}
private fun errorToMap(error: Throwable): WritableMap { private fun errorToMap(error: Throwable): WritableMap {
val map = Arguments.createMap() val map = Arguments.createMap()
map.putString("message", error.message) map.putString("message", error.message)

View File

@ -108,6 +108,7 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
} }
// private properties // private properties
private var isMounted = false
private val reactContext: ReactContext private val reactContext: ReactContext
get() = context as ReactContext get() = context as ReactContext
@ -266,6 +267,10 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
updateLifecycleState() updateLifecycleState()
if (!isMounted) {
isMounted = true
invokeOnViewReady()
}
} }
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {

View File

@ -55,6 +55,7 @@ class CameraViewManager(reactContext: ReactApplicationContext) : SimpleViewManag
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? { override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
return MapBuilder.builder<String, Any>() return MapBuilder.builder<String, Any>()
.put("cameraViewReady", MapBuilder.of("registrationName", "onViewReady"))
.put("cameraInitialized", MapBuilder.of("registrationName", "onInitialized")) .put("cameraInitialized", MapBuilder.of("registrationName", "onInitialized"))
.put("cameraError", MapBuilder.of("registrationName", "onError")) .put("cameraError", MapBuilder.of("registrationName", "onError"))
.put("cameraPerformanceSuggestionAvailable", MapBuilder.of("registrationName", "onFrameProcessorPerformanceSuggestionAvailable")) .put("cameraPerformanceSuggestionAvailable", MapBuilder.of("registrationName", "onFrameProcessorPerformanceSuggestionAvailable"))

View File

@ -65,6 +65,7 @@ public final class CameraView: UIView {
@objc var onInitialized: RCTDirectEventBlock? @objc var onInitialized: RCTDirectEventBlock?
@objc var onError: RCTDirectEventBlock? @objc var onError: RCTDirectEventBlock?
@objc var onFrameProcessorPerformanceSuggestionAvailable: RCTDirectEventBlock? @objc var onFrameProcessorPerformanceSuggestionAvailable: RCTDirectEventBlock?
@objc var onViewReady: RCTDirectEventBlock?
// zoom // zoom
@objc var enableZoomGesture = false { @objc var enableZoomGesture = false {
didSet { didSet {
@ -77,7 +78,7 @@ public final class CameraView: UIView {
} }
// pragma MARK: Internal Properties // pragma MARK: Internal Properties
internal var isMounted = false
internal var isReady = false internal var isReady = false
// Capture Session // Capture Session
internal let captureSession = AVCaptureSession() internal let captureSession = AVCaptureSession()
@ -179,6 +180,17 @@ public final class CameraView: UIView {
object: nil) object: nil)
} }
override public func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
if !isMounted {
isMounted = true
guard let onViewReady = onViewReady else {
return
}
onViewReady(nil)
}
}
// pragma MARK: Props updating // pragma MARK: Props updating
override public final func didSetProps(_ changedProps: [String]!) { override public final func didSetProps(_ changedProps: [String]!) {
ReactLogger.log(level: .info, message: "Updating \(changedProps.count) prop(s)...") ReactLogger.log(level: .info, message: "Updating \(changedProps.count) prop(s)...")

View File

@ -45,10 +45,11 @@ RCT_EXPORT_VIEW_PROPERTY(preset, NSString);
RCT_EXPORT_VIEW_PROPERTY(torch, NSString); RCT_EXPORT_VIEW_PROPERTY(torch, NSString);
RCT_EXPORT_VIEW_PROPERTY(zoom, NSNumber); RCT_EXPORT_VIEW_PROPERTY(zoom, NSNumber);
RCT_EXPORT_VIEW_PROPERTY(enableZoomGesture, BOOL); RCT_EXPORT_VIEW_PROPERTY(enableZoomGesture, BOOL);
// Camera View Properties // Camera View Events
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onFrameProcessorPerformanceSuggestionAvailable, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onFrameProcessorPerformanceSuggestionAvailable, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onViewReady, RCTDirectEventBlock);
// Camera View Functions // Camera View Functions
RCT_EXTERN_METHOD(startRecording:(nonnull NSNumber *)node options:(NSDictionary *)options onRecordCallback:(RCTResponseSenderBlock)onRecordCallback); RCT_EXTERN_METHOD(startRecording:(nonnull NSNumber *)node options:(NSDictionary *)options onRecordCallback:(RCTResponseSenderBlock)onRecordCallback);

View File

@ -30,6 +30,7 @@ type NativeCameraViewProps = Omit<
onInitialized?: (event: NativeSyntheticEvent<void>) => void; onInitialized?: (event: NativeSyntheticEvent<void>) => void;
onError?: (event: NativeSyntheticEvent<OnErrorEvent>) => void; onError?: (event: NativeSyntheticEvent<OnErrorEvent>) => void;
onFrameProcessorPerformanceSuggestionAvailable?: (event: NativeSyntheticEvent<FrameProcessorPerformanceSuggestion>) => void; onFrameProcessorPerformanceSuggestionAvailable?: (event: NativeSyntheticEvent<FrameProcessorPerformanceSuggestion>) => void;
onViewReady: () => void;
}; };
type RefType = React.Component<NativeCameraViewProps> & Readonly<NativeMethods>; type RefType = React.Component<NativeCameraViewProps> & Readonly<NativeMethods>;
//#endregion //#endregion
@ -82,6 +83,7 @@ export class Camera extends React.PureComponent<CameraProps> {
/** @internal */ /** @internal */
constructor(props: CameraProps) { constructor(props: CameraProps) {
super(props); super(props);
this.onViewReady = this.onViewReady.bind(this);
this.onInitialized = this.onInitialized.bind(this); this.onInitialized = this.onInitialized.bind(this);
this.onError = this.onError.bind(this); this.onError = this.onError.bind(this);
this.onFrameProcessorPerformanceSuggestionAvailable = this.onFrameProcessorPerformanceSuggestionAvailable.bind(this); this.onFrameProcessorPerformanceSuggestionAvailable = this.onFrameProcessorPerformanceSuggestionAvailable.bind(this);
@ -367,15 +369,13 @@ export class Camera extends React.PureComponent<CameraProps> {
global.unsetFrameProcessor(this.handle); global.unsetFrameProcessor(this.handle);
} }
componentDidMount(): void { private onViewReady(): void {
requestAnimationFrame(() => {
this.isNativeViewMounted = true; this.isNativeViewMounted = true;
if (this.props.frameProcessor != null) { if (this.props.frameProcessor != null) {
// user passed a `frameProcessor` but we didn't set it yet because the native view was not mounted yet. set it now. // user passed a `frameProcessor` but we didn't set it yet because the native view was not mounted yet. set it now.
this.setFrameProcessor(this.props.frameProcessor); this.setFrameProcessor(this.props.frameProcessor);
this.lastFrameProcessor = this.props.frameProcessor; this.lastFrameProcessor = this.props.frameProcessor;
} }
});
} }
/** @internal */ /** @internal */
@ -411,6 +411,7 @@ export class Camera extends React.PureComponent<CameraProps> {
frameProcessorFps={frameProcessorFps === 'auto' ? -1 : frameProcessorFps} frameProcessorFps={frameProcessorFps === 'auto' ? -1 : frameProcessorFps}
cameraId={device.id} cameraId={device.id}
ref={this.ref} ref={this.ref}
onViewReady={this.onViewReady}
onInitialized={this.onInitialized} onInitialized={this.onInitialized}
onError={this.onError} onError={this.onError}
onFrameProcessorPerformanceSuggestionAvailable={this.onFrameProcessorPerformanceSuggestionAvailable} onFrameProcessorPerformanceSuggestionAvailable={this.onFrameProcessorPerformanceSuggestionAvailable}