Camera mount error

Hello!
When i navigate to my Camera component for the first time, the onCameraReady method is correctly fired, but when i navigate there again, the onMountError method is fired. The docs say that this method returns an object containing a message, but when i try to log the object, i get undefined:
onMountError={(obj) => console.log('Camera mount error', JSON.stringify(obj)).
So i can see the buttons in my render, but the camera screen is black.
I thought the issue might be that the Cam component would still be mounted, but I checked and whenever i leave it, componentWillUnmount is correctly fired.
I’ve noticed that when I place my app in the background and in the foreground again while on the Cam component, the Camera mounts successfully.
I based my code on the doc’s example and removed a lot of things. Here it is (sorry for the long code but since I have no idea which part of the code might be related to the issue, i’d rather post it whole):

export class CameraModule extends Component<IProps, IState> {
    camera: any;
    constructor(props: IProps) {
        super(props);
        this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
        this.state = {
            type: Camera.Constants.Type.back,
            flash: 'off',
            zoom: 0,
            autoFocus: 'on',
            depth: 0,
            whiteBalance: 'auto',
            ratio: '16:9',
            ratios: [],
            showGallery: false,
            permissionsGranted: false,
        };
    }

    async componentWillMount() {
        BackHandler.addEventListener(C.HARDWARE_BACK_PRESS, this.handleBackButtonClick);
        const { status } = await Permissions.askAsync(Permissions.CAMERA);
        this.setState({ permissionsGranted: status === 'granted' });
    }

    componentWillUnmount() {
        BackHandler.removeEventListener(C.HARDWARE_BACK_PRESS, this.handleBackButtonClick);
    }

    handleBackButtonClick() {
        this.props.navigation.goBack(null);
        return true;
    }

    toggleFacing() {
        this.setState({ type: this.state.type === 'back' ? 'front' : 'back' });
    }

    toggleFlash() {
        this.setState({ flash: flashModeOrder[this.state.flash] });
    }

    setRatio(ratio: string) {
        this.setState({ ratio });
    }

    toggleWB() {
        this.setState({ whiteBalance: wbOrder[this.state.whiteBalance] });
    }

    toggleFocus() {
        this.setState({ autoFocus: this.state.autoFocus === 'on' ? 'off' : 'on' });
    }

    zoomOut = () => {
        const { zoom } = this.state;
        if (zoom > 0) {
            this.setState({ zoom: zoom - 0.01 });
        }
    }

    zoomIn = () => {
        const { zoom } = this.state;
        if (zoom < 0.5) {
            this.setState({ zoom: zoom + 0.01 });
        }
    }

    setFocusDepth(depth: number) {
        this.setState({ depth });
    }

    resize = async (url: string) => {
        const manipResult = await ImageManipulator.manipulate(url,
            [{ resize: { height: 512 } }],
            { format: 'jpeg', compress: 1, base64: true },
        );
        return manipResult.base64;
    }

    takePicture = async () => {
        if (this.camera) {
            const { auth: { sessionId }, appReducer: { currentJob: { id } } } = this.props;
            this.props.setIsTakingPicture(true);
            this.camera.takePictureAsync({ base64: true, quality: 0.5 }).then(data => {
                console.log('Photo taken');
                this.resize(data.uri).then(base64 => {
                    this.props.setPictureId(this.props.appReducer.pictureId + 1);
                    setTimeout(() => {
                        const newPic = {
                            id: this.props.appReducer.pictureId,
                            jobId: id,
                            captureDate: moment().valueOf(),
                            picture: base64,
                        };
                        const { appReducer: { localPictures = [] } } = this.props;
                        if (isValid(localPictures)) {
                            this.props.setLocalPictures([...localPictures, newPic]);
                        } else {
                            this.props.setLocalPictures([newPic]);
                        }
                        this.props.setIsTakingPicture(false);
                    }, 0);
                });
            });
        }
    }

    renderNoPermissions() {
        const { t } = this.props;
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', padding: 10 }}>
                <Text>{t('aidooTechnician:noPermission')}</Text>
            </View>
        );
    }

    renderCamera() {
        const { navigation: { navigate }, appReducer: { theme = null, shape = C.SHAPE_ROUND, isTakingPicture }, pictureReducer: { isAddingPicture } } = this.props;
        const { colorMain = C.COLOR_THEME } = theme;
        const { type, flash, autoFocus, zoom, whiteBalance, ratio, depth } = this.state;
        const borderRadius = shape === C.SHAPE_ROUND ? 25 : 0;
        const pictureButtonBorderRadius = shape === C.SHAPE_ROUND ? 40 : 0;
        return (
            <Camera
                ref={ref => this.camera = ref}
                style={{ flex: 1 }}
                type={type}
                flashMode={flash}
                autoFocus={autoFocus}
                zoom={zoom}
                whiteBalance={whiteBalance}
                ratio={ratio}
                focusDepth={depth}
                onCameraReady={() => console.log('Camera ready')}
                onMountError={(message) => console.log('Camera mount error', JSON.stringify(message))}>
                <View style={{ flex: 1, backgroundColor: 'transparent' }}>
                    <View
                        style={{
                            backgroundColor: 'transparent',
                            flexDirection: 'row',
                            justifyContent: 'space-around',
                            paddingTop: Constants.statusBarHeight / 2,
                        }}>
                        <TouchableOpacity style={[s.camButton, { borderRadius, backgroundColor: 'white' }]} onPress={() => this.handleBackButtonClick()}>
                            <Icon name={C.ICON_BACK_BUTTON} />
                        </TouchableOpacity>
                        <TouchableOpacity style={[s.camButton, { borderRadius }]} onPress={() => this.toggleFlash()}>
                            {flash === 'on' && <Image source={require('../../../../assets/camera/flash_on.png')} style={{ height: 30, width: 30 }} />}
                            {flash === 'off' && <Image source={require('../../../../assets/camera/flash_off.png')} style={{ height: 30, width: 30 }} />}
                            {flash === 'auto' && <Image source={require('../../../../assets/camera/flash_auto.png')} style={{ height: 30, width: 30 }} />}
                            {flash === 'torch' && <Icon name={C.ICON_BULB} style={{ fontSize: 30, color: 'white' }} />}
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={[s.camButton, { borderRadius }]}
                            onPress={() => this.toggleFocus()}>
                            <Text style={{ fontWeight: C.NUMBER_BOLD, color: 'white' }}>AF</Text>
                            <Text style={{ color: 'white' }}>{autoFocus} </Text>
                        </TouchableOpacity>
                    </View>
                    {autoFocus !== 'on' ? (
                        <Slider
                            style={{ width: 150, marginTop: 15, marginRight: 15, alignSelf: 'flex-end' }}
                            onValueChange={value => this.setFocusDepth(value)}
                            step={0.1}
                        />
                    ) : null}
                </View>
                <View
                    style={{
                        paddingBottom: isIPhoneX ? 20 : 0,
                        backgroundColor: 'transparent',
                        flexDirection: 'row',
                        alignSelf: 'flex-end',
                    }}>
                    <View style={{ flex: 0.4, alignSelf: 'flex-end' }}>
                        <TouchableOpacity
                            style={[s.camButton, { borderRadius, marginLeft: 10 }]}
                            onPress={() => this.toggleFacing()}>
                            <Icon name={C.ICON_REVERSE_CAMERA} style={{ color: 'white' }} />
                        </TouchableOpacity>
                    </View>
                    <View style={{ flex: 0.2, alignSelf: 'flex-end' }}>
                        <TouchableOpacity
                            style={[s.pictureButton, { borderRadius: pictureButtonBorderRadius }]}
                            onPress={() => this.takePicture()}>
                            <Icon name={C.ICON_CAMERA} style={{ color: 'white' }} />
                        </TouchableOpacity>
                    </View>
                    <View style={{ flex: 0.4, alignItems: 'flex-end' }}>
                        <TouchableOpacity
                            style={[s.camButton, { marginRight: 10, borderRadius }]}
                            onPress={this.zoomIn}>
                            <Text style={{ color: 'white', fontSize: 22 }}> + </Text>
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={[s.camButton, { marginRight: 10, borderRadius }]}
                            onPress={this.zoomOut}>
                            <Text style={{ color: 'white', fontSize: 22 }}> - </Text>
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={[s.camButton, { marginRight: 10, borderRadius, backgroundColor: colorMain }]}
                            onPress={() => navigate(C.SCREEN_GALLERY)}>
                            <Icon name={C.ICON_FORWARD_BUTTON} style={{ color: 'white' }} />
                        </TouchableOpacity>
                    </View>
                </View>
            </Camera >
        );
    }

    render() {
        const { permissionsGranted } = this.state;
        return <View style={{ flex: 1 }}>{permissionsGranted ? this.renderCamera() : this.renderNoPermissions()}</View>;
    }
}

const flashModeOrder = {
    off: 'on',
    on: 'auto',
    auto: 'torch',
    torch: 'off',
};

const wbOrder = {
    auto: 'sunny',
    sunny: 'cloudy',
    cloudy: 'shadow',
    shadow: 'fluorescent',
    fluorescent: 'incandescent',
    incandescent: 'auto',
};

const mapStateToProps = ({ appReducer, auth, pictureReducer }) => ({
    appReducer,
    auth,
    pictureReducer,
});

const mapDispatchToProps = (dispatch: Function) => ({
    setPictureId: (pictureId: number) => dispatch(setPictureId(pictureId)),
    setIsTakingPicture: (isTakingPicture: boolean) => dispatch(setIsTakingPicture(isTakingPicture)),
    setLocalPictures: (localPictures: ILocalPicture[]) => dispatch(setLocalPictures(localPictures)),
});

export default connect(mapStateToProps, mapDispatchToProps)(CameraModule);

Hi, if Camera is mounted on both - first and second screen - it won’t work, because the one that is located in first screen is not being unmounted and you cannot have 2 Camera components (however, this might work on iOS, but not for Android). You can try removing the component while navigating to another screen.

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.