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:
parent
2cf8087ad6
commit
4a73cb96c1
@ -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)
|
||||||
|
@ -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() {
|
||||||
|
@ -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"))
|
||||||
|
@ -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)...")
|
||||||
|
@ -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);
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user