Search code examples
react-nativeandroid-cameraexpo

react-native + Expo fails with Unhandled promise rejection: TypeError: undefined is not an object (evaluating '_this.camera.takePictureAsync')


I am developing in react-native v0.63 and Expo SDK 38. My goal is to read the camera roll off of an android or ios phone and select pictures then story those pictures into an array and display them on the mobile screen. I have found through research that the react-native cameraroll is not supported in Expo SDK 38 and have to use expo-media-library for those functions. As a test I pulled some code from researching which asks for permissions to access the camera and cameraroll. that goes ok. Then I call this.camera.takePictureAsync() to see if I can access the camera to take a picture. simple test. that is where I get the error: Unhandled promise rejection: TypeError: undefined is not an object (evaluating '_this.camera.takePictureAsync')

here is my setup: The full error is:

WARNING
18:17
[Unhandled promise rejection: TypeError: undefined is not an object (evaluating '_this.camera.takePictureAsync')]

Stack trace:
  node_modules\react-navigation\node_modules\@react-navigation\native\lib\module\createNavigationAwareScrollable.js:105:6 in NavigationAwareScrollable#render
  node_modules\regenerator-runtime\runtime.js:45:36 in tryCatch
  node_modules\regenerator-runtime\runtime.js:274:29 in invoke
  node_modules\regenerator-runtime\runtime.js:45:36 in tryCatch
  node_modules\regenerator-runtime\runtime.js:135:27 in invoke
  node_modules\regenerator-runtime\runtime.js:170:16 in PromiseImpl$argument_0
  node_modules\promise\setimmediate\core.js:45:6 in tryCallTwo
  node_modules\promise\setimmediate\core.js:200:22 in doResolve
  node_modules\promise\setimmediate\core.js:66:11 in Promise
  node_modules\regenerator-runtime\runtime.js:169:15 in callInvokeWithMethodAndArg
  node_modules\regenerator-runtime\runtime.js:192:38 in enqueue
  node_modules\regenerator-runtime\runtime.js:219:8 in exports.async
  http://192.168.0.27:19001/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&minify=false&hot=false:253923:41 in _callee
  http://192.168.0.27:19001/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&minify=false&hot=false:254521:108 in onPress
  node_modules\react-native\Libraries\Components\Touchable\TouchableNativeFeedback.js:169:10 in Pressability$argument_0.onPress
  node_modules\react-native\Libraries\Pressability\Pressability.js:655:17 in _performTransitionSideEffects
  node_modules\react-native\Libraries\Pressability\Pressability.js:589:6 in _receiveSignal
  node_modules\react-native\Libraries\Pressability\Pressability.js:499:8 in responderEventHandlers.onResponderRelease
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:286:4 in invokeGuardedCallbackImpl
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:497:2 in invokeGuardedCallback
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:521:2 in invokeGuardedCallbackAndCatchFirstError
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:683:41 in executeDispatch
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:707:19 in executeDispatchesInOrder
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:872:28 in executeDispatchesAndRelease
  [native code]:null in forEach
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:851:4 in forEachAccumulated
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:897:20 in runEventsInBatch
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:1069:18 in runExtractedPluginEventsInBatch
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:2835:35 in batchedUpdates$argument_0
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:20569:13 in batchedUpdates$1
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:2731:29 in batchedUpdates
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:2834:16 in _receiveRootNodeIDEvent
  node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:2911:27 in receiveTouches
  node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:425:19 in __callFunction
  node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:6 in __guard$argument_0
  node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:373:10 in __guard
  node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:111:4 in callFunctionReturnFlushedQueue
  [native code]:null in callFunctionReturnFlushedQueue
  ...

my imports:

import React, { Component } from "react";
import styles from "./styles";
import {  Platform, Image, ImageBackground, TextInput, TouchableWithoutFeedback, TouchableOpacity, Dimensions, FlatList, SafeAreaView,TouchableHighlight, View as RNView, Text, Alert } from "react-native";
import {Container, Grid, Col, Row, Header, Content, Item, Input, View, List, ListItem, Textarea, Button, Form, Accordion,  Left, Right, Body,Thumbnail, Icon, Title, Spinner, Picker, DatePicker, } from 'native-base';
import { Ionicons } from '@expo/vector-icons';
import { FlatGrid } from 'react-native-super-grid';
import {Image as SVGImage} from "react-native-svg";
import Fire from '../Fire';
import firebase from 'firebase';
import * as ImagePicker from 'expo-image-picker';
// import * as Permissions from 'expo-permissions';
import Expo, { Constants, } from 'expo';
import * as MediaLibrary from 'expo-media-library';
import * as Permissions from 'expo-permissions';
import { Camera } from 'expo-camera';

to get permissions I call:

    async componentDidMount() {
        this.getCameraPermissions();
    }

                    
    async getCameraPermissions() {
        console.log('getCameraPermissions: enter function');

        const { status } = await Permissions.askAsync(Permissions.CAMERA);

        if (status === 'granted') {
            console.log("getCameraPermissions: status = "+status);
                this.setState({ cameraGranted: true });
        } else {
            this.setState({ cameraGranted: false });
            console.log('Uh oh! The user has not granted us permission.');
        }

        this.getCameraRollPermissions();
   }

   async getCameraRollPermissions() {
       console.log("getCameraRollPermissions: Enter Function");

       const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL);

       if (status === 'granted') {
           console.log("getCameraRollPermissions: status = "+status);
           this.setState({ rollGranted: true });
       } else {
         console.log('Uh oh! The user has not granted us permission.');
         this.setState({ rollGranted: false });
       }
   }

This is the function call that throws the error. you can see by the console logs it gets into the function and dies on the takepictureasync()

takePictureAndCreateAlbum = async () => {
    console.log('takePictureCreateAlbum: Entering function');
    const { uri } = await this.camera.takePictureAsync()
        .then(() => {
             Alert.alert('takePictureCreateAlbum: taking picture')
        })
        .catch(error => {
             Alert.alert('takePictureCreateAlbum Error!');
        });

    console.log('takePictureCreateAlbum: uri', uri);

    const asset = await MediaLibrary.createAssetAsync(uri);
        console.log('asset', asset);
        MediaLibrary.createAlbumAsync('Expo', asset)
            .then(() => {
                 Alert.alert('Album created!')
            })
            .catch(error => {
                 Alert.alert('An Error Occurred!')
            });
        };

as a test I call it from a button here:

<Button
    style={{flex: 1, justifyContent: "flex-start", backgroundColor: "lightgrey", marginRight: 5}}
    onPress={() => 
        this.state.rollGranted && this.state.cameraGranted
        ? this.takePictureAndCreateAlbum()
        : Alert.alert('Permissions not granted')
        // this.switchToStories();
    }
>

I am testing on an emulated android device and a real android device Samsung S7. both produce the error.

I thought I could trap the error but failed. I also tried returning all values from the call not just the uri and it acted the same. thoughts?


Solution

  • Ok, I hope this helps others that fall into this trap. This error boils down to really understanding the .then .catch error handling.I needed to perform any functions on the uri within the .then()

    a great short article that got me going can be read here: https://medium.com/@lucymarmitchell/using-then-catch-finally-to-handle-errors-in-javascript-promises-6de92bce3afc

    also I was calling camera function wrong:

    const { uri } = await this.camera.takePictureAsync()
        .then(() => {
             Alert.alert('takePictureCreateAlbum: taking picture')
        })
        .catch(error => {
             Alert.alert('takePictureCreateAlbum Error!');
        });
    

    needed the this removed from this.camera.takePictureAsync().