Camera mount error

#1

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);
#2

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.

closed #3

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