The Camera is not ready for foreground service in react native - javascript

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

Related

App crashes on API call in frameProcessor React Native

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);
}, []);

React Native why does the app crash when sending photo to API

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

Expo Barcode Scanner stop working after navigate screen

So, I'm facing a problem when I navigate to my scanner screen and go back the previous screen, then navigate again to my scanner screen, barcode scanner does not working. even console logs does not working. I have to clear cashe and all data from expo app in order to work scanner screen again. I really don't know what causing the porblem but highly suspicious about Navigation. Can anyone help me pls?
Im adding my Scanner Screen right below.
import React, { useState, useEffect } from "react";
import {
Text,
View,
FlatList,
Button,
Modal,
Pressable,
Alert,
StyleSheet,
} from "react-native";
import { BarCodeScanner } from "expo-barcode-scanner";
import axios from "axios";
import { localIP, ngrokServer } from "../constants";
import allStyles from "../components/molecules/Styles";
const styles = allStyles;
export default function ScannerScreen({ navigation }) {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
useEffect(() => {
setReset(false);
});
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
}, []);
const handleBarCodeScanned = async ({ type, data }) => {
setScanned(true);
console.log("Data: ", data);
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.scannerScreenContainer}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
{scanned && reset && (
<Button title={"Tap to Scan Again"} onPress={() => setScanned(false)} />
)}
</View>
);
}
I'm using axios.post and thought maybe that was the cause of problem but when I removed that code block and run again it doesn't scan the QR code.
I had the same issue and I fixed it by adding a listener for focus events (emitted when the screen comes into focus).
This is what it looks like:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something - for example: reset states, ask for camera permission
setScanned(false);
setHasPermission(false);
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);
Try using this code and see if it works.
Source: https://reactnavigation.org/docs/navigation-events/#navigationaddlistener

React Native Firebase update works but hangs app and closes modal

I am attempting to enable a Thumbs Up option where when pressed it retrieves the current number of thumbs up in my database for an item, increments it, then updates it. As you can see I've put 2 "here" console.logs, both log to the screen. I get no errors or catches, and the database updates (immediately when pressed). So it technically works, but then there is a lag/hang, for probably 7 seconds where the thumb icon shows as pressed the whole time like it's stuck, and then the modal goes away back to the main screen. As if it "crashed". If I take the update out, there is no hang and the modal remains open until I choose to close it. So it has to be that code. Do you see any glaring errors in my logic?
Update: I've traced the issue possibly to the screen that renders this component. I populate a flatlist using the firebase onValue, of which each object has this "thumbs up" feature. When I take out the onValue section and replace it with a single dummy data object instead, the thumbs up works perfectly. So there must be a problem with the listener maybe not unsubscribing right the way I have it. Previous screen code added.
import React, {useState} from "react";
import { Image, StyleSheet, TouchableOpacity, View } from "react-native";
import AppText from "../components/AppText";
import * as Sentry from 'sentry-expo';
import { ref, child, get, query, onValue, update } from "firebase/database";
function HotDidItWork({indexStore, db, auth}) {
var user = auth.currentUser;
if (!user){
return (
<></>
)
}
const [upPressed, setUpPressed] = useState(false);
const [displayText, setDisplayText] = useState('');
async function didPressUp(){
setUpPressed(true);
const dbRef = ref(db, 'deals/' + indexStore);
if (displayText == ''){
get(query(dbRef))
.then((usersSnapshot)=> {
if (usersSnapshot.exists()) {
let thumbsUpCount = usersSnapshot.val().thumbsUp;
thumbsUpCount = parseInt(thumbsUpCount);
thumbsUpCount += 1;
console.log('here 1');
update(dbRef, {thumbsUp: thumbsUpCount.toString()})
.then(()=> {
console.log('here 2');
setDisplayText('Thanks!');
})
.catch((e)=> {
console.log('error 1: ' + e.message);
})
} else {
console.log("No data available");
}
})
.catch((e)=> {
console.log("error 2: " + e.message);
})
}
}
return (
<View>
<View style={styles.messageContainer}>
<AppText style={upPressed == false && downPressed == false ? styles.didItWork : styles.pressed}>Did this deal work for you?</AppText>
<AppText style={downPressed == true || upPressed == true ? styles.didItWork : styles.pressed}>{displayText}</AppText>
</View>
<View style={styles.thumbs}>
<TouchableOpacity onPress={() => didPressUp()}>
<Image
style={styles.thumbsUp}
source={require("../assets/thumbsUp.png")}
/>
</TouchableOpacity>
</View>
</View>
);
}
export default HotDidItWork;
Previous Screen that renders flatlist of obj with thumbs up feature (I now believe this is where the error is):
import React, { useState, useEffect } from "react";
import {
FlatList,
Image,
ImageBackground,
Platform,
SafeAreaView,
StyleSheet,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import HotCardFav from "../components/HotCardFav";
import { ref, child, get, query, onValue, update } from "firebase/database";
import ListItemSeparator from "../components/ListItemSeparator";
import CardItemDeleteAction from "../components/CardItemDeleteAction";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { ActivityIndicator } from "react-native";
import {db, auth} from '../../src/config.js';
import AppText from "../components/AppText";
import colors from "../config/colors";
import * as Sentry from 'sentry-expo';
import Header from '../components/Header';
import { ThemeProvider, useFocusEffect } from '#react-navigation/native';
let initialMessagesFav = [];
let listViewRef;
function FavoritesHotScreen() {
var user = auth.currentUser;
if (user == null) {
return (
<></>
)
}
const [loading, setLoading] = useState(false);
const [messagesFav, setMessagesFav] = useState(initialMessagesFav);
const [messagesFavHold, setMessagesFavHold] = useState(initialMessagesFav);
const [refreshing, setRefreshing] = useState(false);
useFocusEffect(
React.useCallback( () => {
async function fetchData() {
// You can await here
const response = await loadListings();
// ...
return () => response();
}
fetchData();
}, [])
);
async function lookupUser(){
const dbRef = ref(db, 'users/' + user.uid);
const usersSnapshot = await get(query(dbRef));
return usersSnapshot;
}
const loadListings = async () => {
let favs = [];
let favsArray = [];
updateInput('');
setLoading(true);
console.log('exists 2');
lookupUser()
.then((snapshot) => {
if (snapshot.exists()) {
favs = snapshot.child("favorites").val();
if (favs != null){
favsArray = favs.split(',');
}
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
snapshot.forEach((childSnapshot)=>{
let found = favsArray.find(function (element) {
return element == childSnapshot.val().indexStore;
});
if (found != undefined){
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().title,
postedDate: childSnapshot.val().postedDate,
desc: childSnapshot.val().desc,
indexStore: childSnapshot.val().indexStore,
})
checkMessages(testData);
setLoading(false);
}
})
})
.catch((error) => Sentry.Native.captureException('Error FavoritesScreen function loadListings 2 ' + error));
}
const renderItem = ({ item }) => (<HotCardFav
title={item.title}
desc={item.desc}
indexStore={item.id}
postedDate={item.postedDate}
/>);
function checkMessages(testData){
const filtered = testData.filter(country => {
return (country.title != 'NA')
})
setMessagesFav(filtered);
setMessagesFavHold(testData);
setLoading(false);
}
let messagesShow = messagesFav.sort((a, b) => {
const messageA = new Date(parseInt(a.postedDate));
const messageB = new Date(parseInt(b.postedDate));
let comparison = 0;
if (messageA > messageB) {
comparison = 1;
} else if (messageA < messageB) {
comparison = -1;
}
return comparison * -1;
});
return (
<SafeAreaView style={styles.wholeThing}>
<Header image={require('../assets/longlogo4.png')} />
<View style={loading ? styles.activity : styles.none}>
<ActivityIndicator animating={loading} size="large" color="#0000ff" />
</View>
<FlatList
data={messagesShow}
keyExtractor={(messagesShow) => messagesShow.id.toString()}
renderItem={renderItem}
ItemSeparatorComponent={ListItemSeparator}
contentContainerStyle={styles.messagesList}
refreshing={refreshing}
ref={(ref) => {
listViewRef = ref;
}}
/>
</SafeAreaView>
);
}
export default FavoritesHotScreen;
Ok I figured it out here's what I found in case it helps someone in the future. The problem was in my main screen loadlistings() function (that I posted as an edit). It uses onValue to retrieve firebase data which attaches a listener which means ANYTIME data on my database changes, it rerenders the flatlist, entirely. Which automatically closes my modal since it's defaulted to be closed when the screen starts. So by me pressing the "thumbs up", it was changing data, the listener responded, rerendered the flatlist with a closed modal. Maybe this should have been obvious but it wasn't for me. I fixed it by adding a simple "onlyOnce" flag to the end of the onValue per firebase documentation:
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
snapshot.forEach((childSnapshot)=>{
let found = favsArray.find(function (element) {
return element == childSnapshot.val().indexStore;
});
if (found != undefined){
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().title,
postedDate: childSnapshot.val().postedDate,
desc: childSnapshot.val().desc,
indexStore: childSnapshot.val().indexStore,
})
checkMessages(testData);
setLoading(false);
})
},{
onlyOnce: true
})

How do I send a function parameter to AsyncStorage?

I want to send the parameter to the function submitLanguageSelection, which is userSelectedLanguage, to a custom hook I've written which (hopefully) saves that parameter to AsyncStorage. The user selects a language, either English or Arabic, from one of the two buttons.
This is my first time ever doing this. I've gotten very stuck.
I would like the submitLanguageSelection function to call the saveData function which is made available through the useLocalStorage hook. I would like the user's choice of language to be persisted in AsyncStorage so I can then later render the ChooseYourLanguageScreen according to whether the user has selected a language or not.
Here is the cutom hook, useLocalStorage:
import React from 'react';
import { Alert } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
const STORAGE_KEY = '#has_stored_value';
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async () => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, storedValue);
if (localValue !== null) {
setStoredValue(storedValue);
Alert.alert('Data successfully saved');
}
console.log('stored val', storedValue);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};
Here is the ChooseYourLanguageScreen:
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import useLocalStorage from '../hooks/useLocalStorage';
const ChooseYourLanguageScreen = ({ navigation }) => {
const [saveData, errorMessage] = useLocalStorage();
const submitLanguageSelection = (userSelectedLanguage) => {
//TODO: save the data locally
//TODO: navigate to welcome screen
// at the moment, the language choice isn't making it to useLocalStorage
if (userSelectedLanguage !== null) {
console.log('user selected lang', userSelectedLanguage);
saveData(userSelectedLanguage);
}
};
return (
<View style={styles.container}>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text style={styles.text}>This is the Choose Your Language Screen</Text>
<View style={styles.buttons}>
<View>
<Button
title={'English'}
onPress={() => submitLanguageSelection('English')}
/>
</View>
<View>
<Button
title={'Arabic'}
onPress={() => submitLanguageSelection('Arabic')}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
alignSelf: 'center',
},
buttons: {
backgroundColor: '#DDDDDD',
padding: 10,
},
});
export default ChooseYourLanguageScreen;
saveData() needs a parameter. You can provide a default value that uses storedValue that came from React.useState(), but when you call it with an explicit argument it will override that default.
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async (dataToSave = storedValue) => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, dataToSave);
if (localValue !== null) {
setStoredValue(dataToSave);
Alert.alert('Data successfully saved');
}
console.log('stored val', dataToSave);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};

Categories

Resources