My app keeps crashing when user tries to send photo from camera to API. There were no problem in the android emulator but it crashes on my physical device (Galaxy A30). The console.log doesn't show anything when I used it on the emulator. There were no problem submitting from image gallery but when submitting from the camera, it crashes.
import React, {useState, useContext} from 'react';
import {ScrollView, View, Text, TextInput, TouchableOpacity, Alert} from 'react-native';
import { AuthContext } from '../Context/AuthContext';
import { URLs } from '../constants/links';
import * as ImagePicker from 'expo-image-picker';
import axios from 'axios';
import * as Permissions from "expo-permissions";
import { CAMERA } from "expo-permissions";
const MyScreen = ({navigation}) => {
const { myToken } = useContext(AuthContext)
const [allImage, setAllImage] = React.useState([]);
const [pickedImage, setPickedImage] = useState("");
const [fileName, setFileName] = React.useState("");
const formdata = new FormData();
const cameraPermission = async () => {
const result = await Permissions.askAsync(CAMERA);
if (result.status != "granted") {
Alert.alert(
"Insufficient Permission",
"You need to grant camera permission to use this app",
[{ text: "Okay" }]
);
return true;
}
return true;
};
const useCamera = async () => {
const hasPermissions = await cameraPermission();
if (!hasPermissions) {
return;
}
if(allImage.length < 4){
let result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 0.5,
});
if (!result.cancelled) {
const name = result.uri.split('/').pop();
let match = /\.(\w+)$/.exec(name);
let type = match ? `image/${match[1]}` : `image`;
let newFile = {
uri: result.uri,
type: type,
name: name
}
setAllImage(newFile)
setPickedImage(result.uri)
if (!pickedImage && allImage.length === 0) {
setAllImage([newFile]);
setFileName("Photo 1")
}else {
setAllImage([...allImage, newFile]);
setFileName(fileName + ", Photo " + (allImage.length + 1))
}
}
} else {
Alert.alert("Image", "You have reach the image upload limit");
}
};
const fetchData = () => {
const abortCont = new AbortController();
allImage.forEach((file) => {
formdata.append('files[]', file);
});
axios({
method: 'post',
url: URLs,
headers: {
Accept: "application/json",
Authorization: myToken,
'Content-Type': "multipart/form-data",
},
data: formdata,
signal: abortCont.signal,
}).then(function (result) {
if(result.data.message === "Successfully added") {
Alert.alert("Upload Successufull", result.data.message);
navigation.goBack()
}
}).catch(function (error) {
Alert.alert("Error", error);
formdata = new FormData();
});
return () => abortCont.abort();
}
return (
<ScrollView>
<View>
<View>
<Text>Attach Receipt File</Text>
<View>
<TextInput
editable={false}
placeholder="Select files.."
value={fileName}
/>
</View>
<View>
<TouchableOpacity activeOpacity={0.8} onPress={useCamera}>
<Text>Camera</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity activeOpacity={0.9} onPress={fetchData}>
<Text>Submit</Text>
</TouchableOpacity>
</View>
</View>
</View>
</ScrollView>
);
}
export default MyScreen;
I still don't know the reason why the app crash when user send photo taken from camera but I found a solution. I change from using camera from expo-permission to Camera from expo-camera. This is the docs: https://docs.expo.dev/versions/latest/sdk/camera/#cameracapturedpicture
Related
I'm trying to take pictures in foreground service. for example taking pictures when the user is in another program. when we are in the app everything is running fine, But when I close the app while the foreground service is active, the camera stops working and gives this error:
`WARN Possible Unhandled Promise Rejection (id: 3):
session/camera-not-ready: [session/camera-not-ready] The Camera is not ready yet! Wait for the onInitialized() callback!`
I am using this for foreground service: https://notifee.app/react-native/docs/android/foreground-service and for camera: https://github.com/mrousavy/react-native-vision-camera
Here is my code:
import React, {useRef, useState, useEffect} from 'react';
import {
Button,
PermissionsAndroid,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
LoadingView,
ActivityIndicator,
Image,
TouchableOpacity,
} from 'react-native';
import {useCameraDevices, Camera} from 'react-native-vision-camera';
import {useIsForeground} from './hooks/useIsForeground';
import RNFS from 'react-native-fs';
import notifee, {AndroidColor} from '#notifee/react-native';
const HelloWorldApp = () => {
const isAppForeground = useIsForeground();
console.log('In Foreground?: ', isAppForeground);
const cameraRef = useRef(null);
const [finalPath, setPhotoPath] = useState('');
const devices = useCameraDevices();
const device = devices.front;
useEffect(() => {
console.log('useEffect');
notifee.registerForegroundService(() => {
console.log('registerForegroundService');
return new Promise(() => {
setInterval(() => {
console.log('setInterval');
const snapShotTaker = async () => {
const snapshot = await cameraRef.current.takeSnapshot({
quality: 20,
skipMetadata: true,
});
console.log(snapshot);
//const path = RNFS.ExternalDirectoryPath + '/photo-X.jpg';
//await RNFS.moveFile(snapshot.path, path);
setPhotoPath('file://' + snapshot.path);
console.log(finalPath);
};
snapShotTaker();
}, 2000);
});
});
}, []);
if (device == null) {
return <ActivityIndicator style={styles.indicator} size="large" />;
}
async function onDisplayNotification() {
// Request permissions (required for iOS)
await notifee.requestPermission();
// Create a channel (required for Android)
const channelId = await notifee.createChannel({
id: 'default',
name: 'Default Channel',
});
// Display a notification
await notifee.displayNotification({
title: 'Foreground service',
body: 'This notification will exist for the lifetime of the service runner',
android: {
channelId,
asForegroundService: true,
color: AndroidColor.RED,
colorized: true,
},
});
}
return (
<View style={styles.container}>
<Camera
ref={cameraRef}
style={styles.camera}
device={device}
isActive={true}
/>
<Image
source={{uri: finalPath + '?' + new Date()}}
style={[styles.image]}
/>
<TouchableOpacity style={styles.button} onPress={onDisplayNotification}>
<Text>Start F Service</Text>
</TouchableOpacity>
</View>
);
};
The notifee docs suggests to register your foreground service early on in your project directory outside of any react component preferably your 'index.js' file
I am using a dependency called vision-camera-code-scanner for QR code scanning in my React Native app. I am getting QR code scan data properly. But i need to pass that data to make an API call. But when i try to do that, it crashes the application. Not sure what should i do here.
Here's my component:
import React, { useState, useCallback, useEffect, useMemo } from "react";
import { StyleSheet, Text } from "react-native";
import {
Camera,
useCameraDevices,
useFrameProcessor,
} from "react-native-vision-camera";
import { useDispatch, useSelector } from "react-redux";
import * as appActions from "../../../redux/app/app.actions";
import { BarcodeFormat, scanBarcodes } from "vision-camera-code-scanner";
interface ScanScreenProps {}
const Scan: React.FC<ScanScreenProps> = () => {
const [hasPermission, setHasPermission] = useState(false);
const devices = useCameraDevices();
const device = devices.back;
const dispatch = useDispatch();
const validateQRStatus = useSelector(validationQRSelector);
const frameProcessor = useFrameProcessor((frame) => {
"worklet";
const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
checkInverted: true,
});
if (detectedBarcodes?.length !== 0) {
const resultObj = JSON.parse(detectedBarcodes[0].rawValue);
const paramData = `token:${Object.values(resultObj)[0]}`;
validate(paramData);
}, []);
const validate = useCallback((param: string) => dispatch(appActions.validateQR(param)));
useEffect(() => {
(async () => {
const status = await Camera.requestCameraPermission();
setHasPermission(status === "authorized");
})();
}, []);
return (
device != null &&
hasPermission && (
<>
<Camera
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
frameProcessor={frameProcessor}
frameProcessorFps={5}
/>
{/* {barcodes.map((barcode, idx) => (
<Text key={idx} style={styles.barcodeTextURL}>
{barcode.barcodeFormat + ": " + barcode.barcodeText}
</Text>
))} */}
<Text style={styles.barcodeTextURL}>camera</Text>
</>
)
);
};
export default Scan;
const styles = StyleSheet.create({
barcodeTextURL: {
fontSize: 20,
color: "white",
fontWeight: "bold",
alignSelf: "center",
},
});
Your problem is that a worklet is run in a separate JS thread. If you need to call any function from your main thread you need to use runOnJS (https://docs.swmansion.com/react-native-reanimated/docs/next/api/miscellaneous/runOnJS/)
import { runOnJS } from 'react-native-reanimated';
const frameProcessor = useFrameProcessor((frame) => {
"worklet";
const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
checkInverted: true,
});
if (detectedBarcodes?.length !== 0) {
const resultObj = JSON.parse(detectedBarcodes[0].rawValue);
const paramData = `token:${Object.values(resultObj)[0]}`;
runOnJS(validate)(paramData);
}, []);
I'm working on a mobile phone application with Stripe and Expo Bar Code Scanner. When you start the application, if you gave the permissions for using the camera, you will can scan bar codes. Bar Codes only contains the id of the scanned item. If it exists, two buttons (+/-) will appear in order to choose the amount for the item. If it doesn't exists, nothing happens. When the amount changes, I save in SecureStore the id of the item as the key and the amount as the value.
The problem is when I move on others screens (with React Navigation) and I came back to scan and I rescan the same item, the amount resets to 0. If you don't give the permissions for the camera, it displays a list of available items when you can choose the amount (+/-) buttons and similar problem.
Here the concerned two files :
ItemListComponent.tsx
import { Button, FlatList, View, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { useState } from 'react';
export const ItemComponent = (props: any) => {
const [amount, setAmount] = useState<number>(0);
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
const save = async () => {
await SecureStore.setItemAsync(props.item.id.toString(), amount.toString());
}
return (
<View>
<Text>{props.item.name}</Text>
<Button
onPress={() => {
setAmount(amount + 1);
save();
}}
title='+'
/>
{amount > 0 &&
<Button
onPress={() => {
setAmount(amount - 1);
save();
}}
title='-'
/>
}
</View>
);
};
export const ItemListComponent = (props: any) => {
return (
<FlatList
data={props.items}
renderItem={({ item }) =>
<ItemComponent key={item.id} item={item} />
}
/>
);
};
BarCodeScannerComponent.tsx
import { BarCodeScanner } from 'expo-barcode-scanner';
import { useState } from 'react';
import { StyleSheet } from 'react-native';
import { ItemComponent } from './ItemListComponent';
import Items from '../models/ItemsModel';
export const BarCodeScannerComponent = () => {
const [item, setItem] = useState<Items>();
const getItem = async ({ data }: any) => {
const response = await fetch(`http://192.168.1.81:8000/items/${data}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const json = await response.json();
setItem(json);
}
}
return (
<View style={styles.container}>
<BarCodeScanner
onBarCodeScanned={getItem}
style={StyleSheet.absoluteFillObject}
/>
{(item !== null && item !== undefined) && <ItemComponent key={item.id} item={item} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
});
Thanks for help !
It looks like you never call getAmount, and if you did call it you'd get infinite recursion.
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
should be
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
}
getAmount();
or, probably even better:
const getAmount = async () => {
const storeAmount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount !== parseInt(storeAmount)) {
setAmount(parseInt(storeAmount));
}
}
useEffect(() => {
getAmount();
}, [props.item.id]);
otherwise, every time it renders you'll call setAmount which will trigger a rerender
Based on the example given in the documentation of expo image picker I'm trying to upload multiple images to AWS Amplify. In the example given on github only one picture is being worked with. Setting the allowsMultipleSelection prop to true makes it possible to pick multiple images but though I've been tinkering with the code to suit it to my need I can't seem to get it.
Here's what I'm doing
import { Amplify, Auth, Storage } from "aws-amplify";
import * as Clipboard from "expo-clipboard";
import * as ImagePicker from "expo-image-picker";
import { useState } from "react";
import { Button, Image, Text, View } from "react-native";
import awsconfig from "../aws-exports";
Amplify.configure(awsconfig);
const UploadImageAWS = () => {
const [image, setImage] = useState([]);
const [percentage, setPercentage] = useState(0);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: "Images",
aspect: [4, 3],
quality: 1,
allowsMultipleSelection: true,
});
this.handleImagePicked(result);
};
handleImagePicked = async (pickerResult) => {
try {
if (!pickerResult.canceled) {
pickerResult.forEach(async (element) => {
setPercentage(0);
const img = await fetchImageFromUri(element.uri);
const uploadUrl = await uploadImage(img.name, img);
downloadImage(uploadUrl);
});
}
} catch (e) {
alert("Upload failed");
}
};
uploadImage = (filename, img) => {
Auth.currentCredentials();
return Storage.put(filename, img, {
level: "public",
contentType: "image/jpeg",
progressCallback(progress) {
setLoading(progress);
},
})
.then((response) => {
return response.key;
})
.catch((error) => {
return error.response;
});
};
const setLoading = (progress) => {
const calculated = parseInt((progress.loaded / progress.total) * 100);
updatePercentage(calculated); // due to s3 put function scoped
};
const updatePercentage = (number) => {
setPercentage(number);
};
downloadImage = (uri) => {
Storage.get(uri)
.then((result) => setImage(result))
.catch((err) => console.log(err));
};
const fetchImageFromUri = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
return blob;
};
const copyToClipboard = () => {
Clipboard.setString(image);
alert("Copied image URL to clipboard");
};
return (
<View style={styles.container}>
<Text style={styles.title}>AWS Storage Upload Demo</Text>
{percentage !== 0 && <Text style={styles.percentage}>{percentage}%</Text>}
{image &&
image.map((img) => (
<View>
<Text style={styles.result} onPress={copyToClipboard}>
<Image
source={{ uri: img }}
style={{ width: 250, height: 250 }}
/>
</Text>
<Text style={styles.info}>Long press to copy the image url</Text>
</View>
))}
<Button onPress={pickImage} title="Pick an image from camera roll" />
</View>
);
};
export default UploadImageAWS;
Running a loop on the handleImagePicked function then having a random name being generated for each picture solved the problem. Here's what the code looks like
imports
import { v4 as uuidv4 } from "uuid";
import * as ImagePicker from "expo-image-picker";
methods logic
let imagesArray = [];
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: "Images",
aspect: [4, 3],
quality: 1,
allowsMultipleSelection: true,
});
result.assets.forEach((image) => handleImagePicked(image));
};
const handleImagePicked = async (pickerResult) => {
const imageName = uuidv4();
try {
if (!pickerResult.canceled) {
const img = await fetchImageFromUri(pickerResult.uri);
const uploadUrl = await uploadImage(imageName, img);
console.log("upload url = ", uploadUrl);
downloadImage(uploadUrl);
}
} catch (e) {
console.log("Upload failed", e.message);
}
};
const uploadImage = async (filename, img) => {
Auth.currentCredentials();
return Storage.put(filename, img, {
level: "public",
contentType: "image/jpeg",
})
.then((response) => {
return response.key;
})
.catch((error) => {
console.log(error);
return error.response;
});
};
const downloadImage = (uri) => {
Storage.get(uri)
.then((result) => {
setImages(result);
imagesArray.push(result);
})
.catch((err) => console.log(err));
};
const fetchImageFromUri = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
// console.log("blob of URI : " + JSON.stringify(blob));
return blob;
};
Images display
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={imagesArray}
renderItem={({ item }) => (
<Image
source={{ uri: item }}
style={{
height: 75,
width: 75,
borderRadius: 10,
marginHorizontal: 10,
resizeMode: "contain",
}}
/>
)}
/>
I am new to react native. I am trying to make a podcast player. While I was able to make it work in my Podcast component, when I change to other component, the audio player current status is not recognized.
I am reusing AudioPlayer component (the one in green) that is working fine in the Podcast component. But how can I have access to the currently playing song from other pages like Home?
This is my AudioPlayer.js that I am trying to reuse in other components. I want to have controls like pause/play, next work from other other components. I am storing the current audio url in redux store but the only thing that I am to do so far is restart the current audio when I click pause from other component.
import React, {useState} from "react";
import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native";
import { AntDesign } from "#expo/vector-icons";
import { connect } from "react-redux";
import { Audio } from "expo-av";
import { audioPlayerAction } from "../actions";
import { radio } from "../assets/radio/radio";
import { styles } from "../assets/styles";
const audio = new Audio.Sound();
const AudioPlayer = (props) => {
// get currently playing song url from redux store
const { audioSrc, index } = props.storeState;
const [currentAudioIndex, setCurrentAudioIndex] = useState(index);
const [currentAudio, setCurrentAudio] = useState(null);
const [loaded, setLoaded] = useState(false);
const [paused, setPaused] = useState(false);
const [nowPlaying, setNowPlaying] = useState("");
const [playerStatus, setPlayerStatus] = useState(null);
Audio.setAudioModeAsync({
staysActiveInBackground: true,
});
// continue playing next song when done
const onPlaybackStatusUpdate = (playbackStatus) => {
if (playbackStatus.didJustFinish) {
next();
}
};
const play = async (url) => {
if (currentAudio !== url) {
await audio.unloadAsync();
try {
const status = await audio.loadAsync(
{
uri: url,
},
{ shouldPlay: true }
);
audio.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
setCurrentAudio(url);
setPlayerStatus(status);
setLoaded(true);
setPaused(false);
setNowPlaying(radio[currentAudioIndex].station);
await audio.playAsync();
} catch (error) {
console.log(error);
}
} else if (currentAudio === url && loaded === true && paused === false) {
await audio.pauseAsync();
setPaused(true);
} else if (currentAudio === url && loaded === true && paused === true) {
await audio.playAsync();
setPaused(false);
}
};
const next = async () => {
let nextIndex = index + 1 >= radio.length ? 0 : index + 1;
setCurrentAudioIndex(nextIndex);
let nextAudio = radio[nextIndex].url;
props.audioPlayerAction(nextAudio, true, null, "podcast", nextIndex);
play(nextAudio);
};
const prev = () => {
let prevSongIndex = index - 1 < 0 ? radio.length - 1 : index - 1;
let prevAudio = radio[prevSongIndex].url;
props.audioPlayerAction(prevAudio, true, null, "podcast", prevSongIndex);
play(prevAudio);
};
return (
<View style={style.container}>
<View style={style.musicTitle}>
<Text style={style.musicTitleText}>Now Playing: {nowPlaying}</Text>
</View>
<View style={style.controls}>
<TouchableOpacity onPress={() => prev()}>
<AntDesign style={style.icon} name="stepbackward" />
</TouchableOpacity>
<TouchableOpacity onPress={() => play(audioSrc)}>
<AntDesign style={style.icon} name={paused ? "play" : "pause"} />
</TouchableOpacity>
<TouchableOpacity onPress={() => next()}>
<AntDesign style={style.icon} name="stepforward" />
</TouchableOpacity>
</View>
</View>
);
};
const MapStateToProps = (state) => {
return { storeState: state.audioPlayerReducer };
};
export default connect(MapStateToProps, { audioPlayerAction })(AudioPlayer);