Expo Camera not mounting properly

#1

I have successfully implemented Expo’s barcode scanner in my app and working wonders. now I need a normal camera to take pictures of documents mainly (app is for internal office use). So I decided to make a simple camera page based on the full example linked in the docs (expo/camerja).

However, I keep getting black screen and it seems that this.camera = ref was never called because I keep getting undefined for this.camera, which implies any other functions that depends on this can’t be tested either, and I can only assume that this means camera component itself was never mounted.

Not sure what I’m missing here. What I’ve done:

  • Check if the barcode scanner was mounted in any way, so I even tried deleting the part imported the scanner in my js file.
  • tried logging onCameraReady activity and it’s not even triggered.

Here’s my code:

import React from 'react';
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
import { Camera, Permissions, Constants } from 'expo';
import { Actions } from 'react-native-router-flux';

class CameraPage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            hasCameraPermission: null,
            type: Camera.Constants.Type.back,
            pictureSize: undefined,
            pictureSizes: [],
            pictureSizeId: 0,
            zoom: 0,
        }

        
    }

    componentDidMount() {
        this._requestCameraPermission();
    }

    _requestCameraPermission = async () => {
        const { status } = await Permissions.askAsync(Permissions.CAMERA);
        this.setState({
            hasCameraPermission: status === 'granted',
        });
    };

    takePicture = () => {
        if (this.camera) {
            this.camera.takePictureAsync({ onPictureSaved: this.onPictureSaved });
        }
    };

    onPictureSaved = async photo => {
        this.props.pictureTaken(photo.uri);
    }

    collectPictureSizes = async () => {
        console.log(this.camera)
        if (this.camera) {
            const pictureSizes = await this.camera.getAvailablePictureSizesAsync('4:3');
            let pictureSizeId = 0;
            if (Platform.OS === 'ios') {
                pictureSizeId = pictureSizes.indexOf('High');
            } else {
                // returned array is sorted in ascending order - default size is the largest one
                pictureSizeId = pictureSizes.length - 1;
            }
            this.setState({ pictureSizes, pictureSizeId, pictureSize: pictureSizes[pictureSizeId] });
        }
    };

    zoomOut = () => this.setState({ zoom: this.state.zoom - 0.1 < 0 ? 0 : this.state.zoom - 0.1 });
    zoomIn = () => this.setState({ zoom: this.state.zoom + 0.1 > 1 ? 1 : this.state.zoom + 0.1 });

    zoomButtons() {
        return (
            <View style={styles.zoomContainer}>
                <IconWrapper name="zoom-out" size={40} color={color.white} onPress={this.zoomOut} style={{ padding: 6 }} />
                <IconWrapper name="zoom-in" size={40} color={color.white} onPress={this.zoomIn} style={{ padding: 6 }} />
            </View>
        );
    }

    previousPictureSize = () => this.changePictureSize(1);
    nextPictureSize = () => this.changePictureSize(-1);

    changePictureSize = direction => {
        let newId = this.state.pictureSizeId + direction;
        const length = this.state.pictureSizes.length;
        if (newId >= length) {
            newId = 0;
        } else if (newId < 0) {
            newId = length - 1;
        }
        this.setState({ pictureSize: this.state.pictureSizes[newId], pictureSizeId: newId });
    };

    pictureSizePicker() {
        return (<View style={styles.pictureSizeContainer}>
            <Text style={styles.pictureQualityLabel}>Picture quality</Text>
            <View style={styles.pictureSizeChooser}>
                <IconWrapper name="chevron-left" size={40} color={color.white} onPress={this.previousPictureSize} style={{ padding: 6 }} />
                <View style={styles.pictureSizeLabel}>
                    <Text style={styles.textStyle}>{this.state.pictureSize}</Text>
                </View>
                <IconWrapper name="chevron-right" size={40} color={color.white} onPress={this.nextPictureSize} style={{ padding: 6 }} />
            </View>
        </View>);
    }

    cameraModeButton() {
        return (<IconWrapper name="ios-reverse-camera-outline" type='ionicon' size={40} color={color.white} onPress={() => {
            this.setState({
                type: this.state.type === Camera.Constants.Type.back
                    ? Camera.Constants.Type.front
                    : Camera.Constants.Type.back,
            });
        }} style={{ flex: 1, justifyContent: 'center' }} />);
    }

    render() {
        const { hasCameraPermission } = this.state;
        
        if (hasCameraPermission === null) {
            return <View />;
        } else if (hasCameraPermission === false) {
            return <Text>No access to camera</Text>;
        } else {
            return (
                <View style={{ flex: 1 }}>
                    <Camera
                        ref={ref => { this.camera = ref; }}
                        style={styles.camera}
                        onCameraReady={this.collectPictureSizes}
                        type={this.state.type}
                        flashMode={Camera.Constants.FlashMode.auto}
                        autoFocus={Camera.Constants.AutoFocus.on}
                        zoom={this.state.zoom}
                        whiteBalance={Camera.Constants.WhiteBalance.auto}
                        pictureSize={this.state.pictureSize}>
                        <View style={styles.topBar}>
                            {this.pictureSizePicker()}
                            <IconWrapper name='close' color={color.white} size={40} onPress={() => Actions.pop()} style={{ flex: 0.3, padding: 6 }} />
                        </View>
                        <View style={styles.bottomBar}>
                            {this.zoomButtons()}
                            <IconWrapper name='radio-button-unchecked' color={color.white} size={50} onPress={this.takePicture} style={{ padding: 6 }} />
                            {this.cameraModeButton()}
                        </View>
                    </Camera>
                </View>
            );
        }
    }
}

export default CameraPage;

Can anyone point me in the right direction here?

Thanks in advance :slight_smile:

UPDATE:
after trying it on actual phone it seems that the camera actually opened but still none of the callbacks are triggered, so there might be something wrong with the way i’m configuring it?
this.camera staying undefined seems to be the root.

#2

Could we see styles? Also – try maybe just substituting a View with that styles.camera with no props in for now and seeing if the ref is set?

#3

@nikki thanks for the prompt reply :slight_smile:
not sure if I get what u mean, is it something like this?

 <View style={styles.camera}>
                    <Camera
                        ref={ref => this.camera = ref}
                        style={}
                        onCameraReady={this.collectPictureSizes}
                        type={this.state.type}
                        flashMode={Camera.Constants.FlashMode.auto}
                        autoFocus={Camera.Constants.AutoFocus.on}
                        zoom={this.state.zoom}
                        whiteBalance={Camera.Constants.WhiteBalance.auto}
                        pictureSize={this.state.pictureSize}>
                        <View style={styles.topBar}>
                            {this.pictureSizePicker()}
                            <IconWrapper name='close' color={color.white} size={40} onPress={() => Actions.pop()} style={{ flex: 0.3, padding: 6 }} />
                        </View>
                        <View style={styles.bottomBar}>
                            {this.zoomButtons()}
                            <IconWrapper name='radio-button-unchecked' color={color.white} size={50} onPress={this.takePicture} style={{ padding: 6 }} />
                            {this.cameraModeButton()}
                        </View>
                    </Camera>
                </View>

Anyway, here’s the styles:

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#000',
    },
    camera: {
        flex: 1,
        justifyContent: 'space-between',
    },
    zoomContainer: {
        alignItems: 'center',
        justifyContent: 'space-between',
        flexDirection: 'row'
    },
    pictureSizeContainer: {
        flex: 0.5,
        alignItems: 'center',
        paddingTop: 10,
    },
    pictureQualityLabel: {
        fontSize: fontSize.large - 2,
        fontFamily: fontFamily.regular,
        marginVertical: 3,
        color: color.white
    },
    pictureSizeChooser: {
        alignItems: 'center',
        justifyContent: 'space-between',
        flexDirection: 'row'
    },
    pictureSizeLabel: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center'
    },
    textStyle: {
        color: color.white,
        fontFamily: fontFamily.regular,
        fontSize: fontSize.large
    },
    topBar: {
        paddingTop: Constants.statusBarHeight / 2,
        backgroundColor: 'transparent',
        flexDirection: 'row',
        justifyContent: 'space-around',
    },
    bottomBar: {
        position: 'absolute',
        bottom: 0,
        flexDirection: 'row',
        backgroundColor: 'transparent',
        justifyContent: 'space-between',
    }
});

Do you mean you suspect that this might have something to do with a view blocking the camera page?

#4

it seems that it could be performance related as well, because as soon as I tried clicking on any button that’s not close button (which just Actions.pop() this scene) the app will freeze like shown and sometimes crash.

Weird tho, this function just does a simple calculation, problem must be when the state changes and the page tries to re-render and give camera a new zoom props…

Please let me know what you think the problem could be I can only guess with my 4 months exp in RN community lol

UPDATE:
replacing camera with barcode scanner seems to work just fine, must be a camera specific problem then? Did you guys end up basing barcodeScanner on camera btw (I’m using sdk27)? If so, this case just gotten abit weirder :confused:

#5

Hmm yeah this sounds like a real issue. Could you open an issue on github.com/expo/expo and we’ll track it?

#6

@nikki sure thing! thanks for the help! :slight_smile:

Here it is!

closed #7

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