NetInfo crash in android - react-native

I'm using NetInfo to get the connection but some devices android crash with that
LOG:
Error Tag: Mini App Bundle Message: null Stack: android.net.ConnectivityManager$TooManyRequestsException at android.net.ConnectivityManager.convertServiceException(ConnectivityManager.java:3687) at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:3924) at android.net.ConnectivityManager.registerDefaultNetworkCallback(ConnectivityManager.java:4334) at android.net.ConnectivityManager.registerDefaultNetworkCallback(ConnectivityManager.java:4311) at com.reactnativecommunity.netinfo.NetworkCallbackConnectivityReceiver.register(NetworkCallbackConnectivityReceiver.java:42) at com.reactnativecommunity.netinfo.NetInfoModule.initialize(NetInfoModule.java:38) at com.facebook.react.bridge.ModuleHolder.doInitialize(ModuleHolder.java:222) at com.facebook.react.bridge.ModuleHolder.markInitializable(ModuleHolder..java:97) at com.facebook.react.bridge.NativeModuleRegistry.notifyJSInstanceInitialized(NativeModuleRegistry.java:102) at com.facebook.react.bridge.CatalystInstanceImpl$2.run(CatalystInstanceImpl.java:441) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:26) at android.os.Looper.loop(Looper.java:237) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:225) at java.lang.Thread.run(Thread.java:919)
In my code:
import MaxApiManager from '#common/MaxApiManager';
import { throttle } from 'lodash';
import React, { useEffect, useState } from 'react';
import { View, TouchableOpacity, Image, StyleSheet, Text } from 'react-native';
import ChatImages from 'src/screens/Chat/img';
import { mapNameWithLocalContact } from 'src/screens/Chat/utils/ChatUtils';
import { Avatar, Skeleton } from '#momo-platform/component-kits';
import NetInfo from '#react-native-community/netinfo';
export function HeaderConversation({
relationShip,
relationshipText,
friendInfo = {},
goToProfile = () => {},
}) {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
NetInfo?.fetch()?.then(state => {
setIsConnected(state.isConnected);
});
}, []);
return (
<View style={styles.headerLeft}>
<TouchableOpacity
style={styles.buttonBack}
onPress={throttle(() => MaxApiManager.dismiss(), 1000, {
leading: true,
trailing: false,
})}
>
<Image source={ChatImages.ic_back} style={styles.icBack} />
</TouchableOpacity>
{relationShip || isConnected === false ? (
<TouchableOpacity onPress={goToProfile} style={styles.info}>
<Avatar
size="small"
name={friendInfo?.name}
source={{ uri: friendInfo?.avatar }}
/>
<View style={{ marginLeft: 12 }}>
<Text style={styles.txtRoomName}>
{mapNameWithLocalContact({
phone: friendInfo?.phone,
name: friendInfo?.name,
})}
</Text>
{relationShip && <Text>{relationshipText}</Text>}
</View>
</TouchableOpacity>
) : (
<View style={{ paddingTop: 12 }}>
<Skeleton.Custom
left={<Skeleton.Media size={35} />}
style={styles.skeletonItem}
>
<Skeleton.Line style={styles.width_1_9} />
<Skeleton.Line style={styles.width_1_10} />
</Skeleton.Custom>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
headerLeft: {
paddingLeft: 8,
flexDirection: 'row',
alignItems: 'center',
},
buttonBack: { padding: 8, marginRight: 4 },
width_1_9: { width: 150, height: 16 },
width_1_10: { width: 100, height: 12, marginTop: -6 },
skeletonItem: {
marginVertical: 5,
},
info: {
flexDirection: 'row',
alignItems: 'center',
},
txtRoomName: { fontSize: 16, fontWeight: 'bold' },
icBack: { width: 24, height: 24 },
});
AnalyticsModule.js
const AnalyticModule = {
netInfoSub: null,
initialize(deviceInfo) {
UserProfile.getInstance().getData().then((profile) => {
Storage.getItem("", (ipAddress) => {
StorageCache.get("").then(location => {
if (!this.netInfoSub) {
this.netInfoSub = NetInfo.addEventListener((state) => {
let netInfo = getNetworkInfo(state);
this.TRACKING_NETWORK_INFO = JSON.stringify(netInfo);
})
}
})
})
})
},
}
More infor:
#react-native-community/netinfo: ^5.9.9
react-native: 0.61.5

The easiest way to obtain network status information in your functional component is by using useNetInfo hook
import {useNetInfo} from '#react-native-community/netinfo';
const YourComponent = () => {
const netInfo = useNetInfo();
return (
<View>
<Text>Type: {netInfo.type}</Text>
<Text>Is Connected? {netInfo.isConnected.toString()}</Text>
</View>
);
};
for more details on property object: netinfo Docs

We have an eventListener for NetInfo and the mistake is we don't remove this event and with android, NetworkCallbacks have a limit(Maybe is 100 enter link description here)

Related

React Native: Camera from "expo-camera" stop running when face is not ever detected

I am newer for using react-native, and wanna try to create a camera with filter. I'm blocked in step to recognize face. Have success to draw rectangle when face detected, but the problem is once it goes out of detection. The camera stop running as it fixes on the last real-time capture
Here is my code:
import { useState, useEffect, useRef } from 'react'
import { Camera } from 'expo-camera'
import * as MediaLibrary from 'expo-media-library'
import { Text, StyleSheet, View, TouchableOpacity } from 'react-native'
import Button from './Button'
import { Ionicons } from '#expo/vector-icons'
import * as FaceDetector from 'expo-face-detector'
export default function PCamera() {
const cameraRef = useRef(undefined)
const [faceDetected, setFaceDetected] = useState([])
const [lastImage, setImage] = useState(undefined)
const [hasUsePermssion, setUsePermission] = useState(false)
const [type, switchToType] = useState(Camera.Constants.Type.front)
const takePicture = async () => {
if (cameraRef) {
try {
const options = {
quality: 1,
base64: true,
exif: false,
}
const data = await cameraRef.current.takePictureAsync(options)
setImage(data.uri)
console.log(data)
} catch (err) {
console.error(err)
}
}
}
const swithMode = () => {
switchToType(
type === Camera.Constants.Type.front
? Camera.Constants.Type.back
: Camera.Constants.Type.front
)
}
const handleFacesDetected = ({ faces }) => {
setFaceDetected(faces)
}
useEffect(() => {
;(async () => {
const { status } = await Camera.requestCameraPermissionsAsync()
if (status === 'granted') {
setUsePermission(true)
}
})()
}, [])
if (hasUsePermssion === null) {
return <View />
}
if (hasUsePermssion === false) {
return <Text>No access to camera</Text>
}
return (
<View style={styles.cameraContainer}>
<View style={styles.overlay}>
<Camera
ref={cameraRef}
style={styles.camera}
type={type}
onFacesDetected={handleFacesDetected}
faceDetectorSettings={{
mode: FaceDetector.FaceDetectorMode.fast,
detectLandmarks: FaceDetector.FaceDetectorLandmarks.all,
runClassifications:
FaceDetector.FaceDetectorClassifications.none,
minDetectionInterval: 100,
tracking: true,
}}
>
{faceDetected.length > 0 &&
faceDetected.map((face) => (
<View
key={face.faceID}
style={{
position: 'absolute',
borderWidth: 2,
borderColor: 'red',
left: face.bounds.origin.x,
top: face.bounds.origin.y,
width: face.bounds.size.width,
height: face.bounds.size.height,
}}
/>
))}
</Camera>
</View>
<View style={styles.optionsContainer}>
<View>
<TouchableOpacity onPress={swithMode}>
<Text>
<Ionicons
name="camera-reverse-outline"
size={24}
color="black"
/>
</Text>
</TouchableOpacity>
</View>
<Button
icon="camera"
title="Take Photo"
onPress={takePicture}
style={styles.button}
/>
<View>
<Text>...</Text>
</View>
</View>
</View>
)}
const styles = StyleSheet.create({
cameraContainer: {flex: 1,
},
overlay: {
flex: 6,
borderBottomStartRadius: 75,
borderBottomEndRadius: 75,
overflow: 'hidden',
},
camera: {
flex: 1,
},
optionsContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
})
N.B: Don't take care of the Button, it's a custom component and works well

BottomSheet (#gorhom/bottom-sheet) doesn't drag the sheet up or down (iOS Only)

Sometimes BottomSheet doesn't showing, I think it happens because of conflict with. When I build release APK and install it on IOS, when you open application first time then bottom sheet doesn't show but if you close and reopen application then bottom sheet appears. Had anyone this problem? This issue not appear in android, it issue only in IOS
import React, {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import {
Text,
StyleSheet,
Image,
Linking,
TouchableOpacity,
View,
Platform,
} from 'react-native';
import { Box, AbstractButton } from '../../components';
import { COLORS, assets, FONTS, FONT_WEIGHT, SIZES } from '../../constants';
import { RootStackParamList } from '../../navigation/types';
import { NativeStackScreenProps } from '#react-navigation/native-stack';
import { useSelector } from 'react-redux';
import { RootState } from '../../state/rootReducer';
import { useAppDispatch } from '../../state/index';
import { setProfilePhoto, setUserId } from '../../state/onboarding';
import { setIsBottomSheet, setIsEnableHeader } from '../../state/generalUtil';
import BottomSheet, { BottomSheetView } from '#gorhom/bottom-sheet';
import ProfilePhotoBottomSheet from './ProfilePhotoBottomSheet';
import { useBackHandler, useDimensions } from '#react-native-community/hooks';
import {
addDoitLater,
createUserOnboarding,
getDoitLater,
getUserDataById,
uploadProfileImage,
} from '../../service/OnboardingService';
import {
setBarStyle,
setStatusbarColor,
setTranslucent,
} from '../../state/generalUtil';
import { CustomCamera } from '../../components/CameraScreen';
// import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import ApiUtil from '../../util/ApiUtil';
import { CommonActions } from '#react-navigation/native';
import ImagePicker from 'react-native-image-crop-picker';
import PermissionService from '../../service/PermissionService';
import { InfoPopUp } from '../../components/PopUpModal';
import CameraInfoPopup from './CameraInfoPopup';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
const ProfilePhoto = ({
route,
navigation,
}: NativeStackScreenProps<RootStackParamList>) => {
const { profilePhoto, userId } = useSelector(
(state: RootState) => state.onboarding,
);
const { PERMISSIONS_TYPE, checkPermissions, requestPermisssions } =
PermissionService;
const [isSelected, setIsSelected] = useState(false);
const [openCamera, setOpenCamera] = useState(false);
const [visible, setVisible] = useState(false);
const dispatch = useAppDispatch();
const { screen, window } = useDimensions();
// var ImagePicker = NativeModules.ImageCropPicker;
useEffect(() => {
if (openCamera) {
dispatch(setStatusbarColor('#000000'));
dispatch(setBarStyle('light-content'));
dispatch(setIsBottomSheet(true));
dispatch(setTranslucent(false));
dispatch(setIsEnableHeader(false));
} else {
dispatch(setStatusbarColor('#F2F2EF'));
dispatch(setBarStyle('dark-content'));
dispatch(setIsBottomSheet(false));
dispatch(setTranslucent(false));
}
}, [openCamera, dispatch]);
// BottomSheet Hooks
const sheetRef = useRef<BottomSheet>(null);
// Keep Tracking bootom sheet is open or not.
const [isOpen, setIsOpen] = useState(false);
// BottomSheet snap variables
const snapPoints = ['38%'];
// BottomSheet Callbacks
const handleClosePress = useCallback(() => {
sheetRef.current?.close();
}, []);
const handleCloseModal = useCallback(() => {
setVisible(false);
}, []);
const UpdateProfilePhoto = async () => {
const userData: any = await getUserDataById();
if (userData?.basicInfo?.userId) {
await uploadProfileImage(profilePhoto.url, userData?.basicInfo?.userId);
dispatch(setUserId(userData.basicInfo?.userId));
}
};
// BottomSheet Callbacks
const handleSnapPress = useCallback(index => {
sheetRef.current?.snapToIndex(index);
setIsOpen(true);
}, []);
const handleTakePhoto = async () => {
const PermissionStatus = await checkPermissions(PERMISSIONS_TYPE.camera);
if (PermissionStatus === 'blocked') {
setVisible(true);
}
const permission = await requestPermisssions(PERMISSIONS_TYPE.camera);
if (permission === 'granted') {
setOpenCamera(true);
}
};
const handleCloseCam = (image?: any) => {
if (image) {
dispatch(setProfilePhoto(image.uri));
}
setOpenCamera(false);
setIsOpen(false);
};
const handleSelectPhoto = () => {
ImagePicker.openPicker({
width: 300,
height: 400,
mediaType: 'photo',
}).then(async image => {
setIsSelected(true);
dispatch(setProfilePhoto(image.path));
});
};
const handleContinue = () => {
if (userId) {
UpdateProfilePhoto();
createUserOnboarding(userId, 10);
}
if (route?.params?.doItLaterFlag === 10) {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'ThingsToDoScreen' }],
}),
);
} else {
navigation.navigate('ConfirmDetails');
}
};
const doItLater = async () => {
dispatch(setProfilePhoto(''));
if (route?.params?.doItLaterFlag === 10) {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'ThingsToDoScreen' }],
}),
);
} else {
navigation.navigate('ConfirmDetails');
}
};
useBackHandler(
useCallback(() => {
if (isOpen) {
sheetRef.current?.close();
} else {
navigation.goBack();
}
return true;
}, [isOpen]),
);
return (
<>
{openCamera ? (
<Box width={window.width} height={window.height - 40}>
<CustomCamera type={'front'} close={handleCloseCam} />
</Box>
) : (
<>
<Box style={isOpen ? styles.containerOpacity : styles.container}>
<InfoPopUp visible={visible} handleCloseModal={handleCloseModal}>
{<CameraInfoPopup handleCloseModal={handleCloseModal} />}
</InfoPopUp>
<Box>
<Box
pt={145}
flexDirection={'column'}
style={{ alignItems: 'center' }}>
<TouchableOpacity
disabled={isOpen}
onPress={() => {
handleSnapPress(1);
}}>
<View style={styles.photoView}>
{profilePhoto.url ? (
<>
<Box>
<Image
source={{ uri: profilePhoto.url }}
style={styles.image}
/>
</Box>
<Box style={styles.insideImg3}>
<Image
source={assets.EditPen}
style={styles.PlusIconImage}
/>
</Box>
</>
) : (
<>
<Box style={styles.insideImg1}>
<Image
source={assets.Smile}
style={styles.SmileImage}
/>
</Box>
<Box style={styles.insideImg2}>
<Image
source={assets.PlusIcon}
style={styles.PlusIconImage}
/>
</Box>
</>
)}
</View>
</TouchableOpacity>
<Text style={styles.text}>Add profile photo</Text>
<Text style={styles.text1}>
To promote a safe and transparent community, we recommend a
clear photo of yourself
</Text>
</Box>
</Box>
</Box>
<Box style={styles.continueBtn}>
<AbstractButton
disabled={!profilePhoto.url}
onPress={handleContinue}>
Continue
</AbstractButton>
<TouchableOpacity onPress={doItLater}>
<Text style={styles.text2}>Do it later</Text>
</TouchableOpacity>
</Box>
{/* Bottom Sheet */}
{isOpen && (
<BottomSheet
ref={sheetRef}
snapPoints={snapPoints}
enablePanDownToClose={true}
onClose={() => setIsOpen(false)}
backgroundStyle={{
backgroundColor: COLORS.background.primary,
borderColor: COLORS.gray,
borderWidth: 2,
}}>
<GestureHandlerRootView>
<BottomSheetView>
<ProfilePhotoBottomSheet
route={route}
navigation={navigation}
handleTakePhoto={handleTakePhoto}
handleSelectPhoto={handleSelectPhoto}
handleClosePress={handleClosePress}
/>
</BottomSheetView>
</GestureHandlerRootView>
</BottomSheet>
)}
</>
)}
</>
);
};
const styles = StyleSheet.create({
continueBtn: {
position: 'absolute',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'center',
width: '100%',
paddingHorizontal: 10,
top:
Platform.OS === 'ios'
? SIZES.screen_height / 1.4
: SIZES.screen_height / 1.35,
},
container: {
flex: 1,
paddingHorizontal: 20,
backgroundColor: COLORS.background.primary,
},
containerOpacity: {
flex: 1,
opacity: 0.5,
paddingHorizontal: 20,
// backgroundColor: COLORS.lightGray,
},
container1: {
backgroundColor: COLORS.background.primary,
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 20,
},
SmileImage: {
width: 65,
height: 64,
},
PlusIconImage: {
width: 40,
height: 40,
},
text: {
paddingTop: '10%',
color: COLORS.black,
fontSize: SIZES.extraLarge,
fontFamily: FONTS.PlayfairDisplayBold,
lineHeight: 30,
alignItems: 'center',
},
text1: {
fontSize: SIZES.font,
fontFamily: FONTS.MerriweatherRegular,
lineHeight: 24,
color: '#2B2928',
paddingHorizontal: '15%',
paddingVertical: '4%',
textAlign: 'center',
},
text2: {
paddingTop: '8%',
color: COLORS.black,
fontFamily: FONTS.MerriweatherBold,
fontSize: 13,
lineHeight: 21,
textAlign: 'center',
},
photoView: {
width: 150,
height: 150,
backgroundColor: COLORS.white,
borderRadius: 100,
},
insideImg1: {
top: '30%',
left: '28%',
},
insideImg2: {
top: '30%',
left: '70%',
},
insideImg3: {
top: '-30%',
left: '70%',
},
image: {
width: 150,
height: 150,
borderRadius: 100,
},
});
export default ProfilePhoto;
Update: Different similar issue but on Android
I was also having this exact problem, my BottomSheetModals and BottomSheetScrollViews were not responding to touch events on Android.
Root Cause
react-native-gesture-handler requires a wrapper around the root view to function. In v1 of the library, this was done on the native side, and expo did this for you. In v2 of the library, you must use the GestureHandlerRootView in your app manually. Upgrading to SDK 44 of expo removes the native RNGH setup.
FIX
The GestureHandlerRootView must be applied as high as possible in your app's component tree.
In my case, I had my BottomSheetModalProvider outside the GestureHandlerRootView, and swapping these two components fixed the issue for me!
Before:
<BottomSheetModalProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<MainNavigation />
</GestureHandlerRootView>
</BottomSheetModalProvider>
After:
<GestureHandlerRootView style={{ flex: 1 }}>
<BottomSheetModalProvider>
<MainNavigation />
</BottomSheetModalProvider>
</GestureHandlerRootView>
#gorhom does it make sense to update the docs to say that BottomSheetModalProvider must be inside GestureHandlerRootView?
make sure to wrap your whole app with
<GestureHandlerRootView style={{flex:1}}>
{your other components}
</GestureHandlerRootView>
and read the gesture handler docs for installation and setup tips if this doesn't solve your issue
you might also need to add import 'react-native-gesture-handler'; at the beginning of your index.js
Finally, I got a solution for this issue. don't wrap with Ternary operator the bottomsheet. pls reffer below code.
Before :
{isOpen && (
<BottomSheet
ref={sheetRef}
snapPoints={snapPoints}
enablePanDownToClose={true}
onClose={() => setIsOpen(false)}
backgroundStyle={{
backgroundColor: COLORS.background.primary,
borderColor: COLORS.gray,
borderWidth: 2,
}}>
<GestureHandlerRootView>
<BottomSheetView>
<ProfilePhotoBottomSheet
route={route}
navigation={navigation}
handleTakePhoto={handleTakePhoto}
handleSelectPhoto={handleSelectPhoto}
handleClosePress={handleClosePress}
/>
</BottomSheetView>
</GestureHandlerRootView>
</BottomSheet>
)}
</>
)}
After fix :
<BottomSheet
index={-1}
ref={sheetRef}
snapPoints={snapPoints}
enablePanDownToClose={true}
onClose={() => setIsOpen(false)}
backgroundStyle={{
backgroundColor: COLORS.background.primary,
borderColor: COLORS.gray,
borderWidth: 2,
}}>
<BottomSheetView>
<ProfilePhotoBottomSheet
route={route}
navigation={navigation}
handleTakePhoto={handleTakePhoto}
handleSelectPhoto={handleSelectPhoto}
handleClosePress={handleClosePress}
/>
</BottomSheetView>
</BottomSheet>
```

How to remove/replace default Profile Picture with Image Picker In React Native Expo

I am looking for assistance on how to replace a default profile picture with the one a user would select from their media library.
I have managed to create an onPress function that allows the user to select the image from their media library. The image is returned and displayed also in the prescribed layout.
My problem is that I cannot see the default profile picture, but I can see and click on a pencil icon to prompt the image-picker for iOS and Android as I am using Expo.
Here is my custom component code:
import React, { useState, useEffect } from "react";
import {
StyleSheet,
View,
Text,
Image,
TouchableOpacity,
useWindowDimensions,
Platform,
} from "react-native";
import { Controller } from "react-hook-form";
import * as ImagePicker from "expo-image-picker";
import { Ionicons } from "#expo/vector-icons";
//import dependencies
import { COLORS, SIZES, images } from "../constants";
const CustomImagePicker = ({ control, name, rules = {} }) => {
const { height } = useWindowDimensions();
const [hasGalleryPermission, setHasGalleryPermission] = useState("false");
const [profilePicture, setProfilePicture] = useState(name);
useEffect(() => {
async () => {
const galleryStatus =
await ImagePicker.requestMediaLibraryPermissionsAsync();
setHasGalleryPermission(galleryStatus.status === "granted");
};
}, []);
const pickImage = async () => {
let chosenImage = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
console.log(JSON.stringify(chosenImage));
if (!chosenImage.cancelled) {
setProfilePicture(chosenImage.uri);
}
};
if (hasGalleryPermission === false) {
return <Text>❌ No access to Internal Storage</Text>;
}
return (
<Controller
name={name}
control={control}
rules={rules}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<View
style={[
styles.container,
{ borderColor: error ? COLORS.red : COLORS.gray },
]}
>
<TouchableOpacity
style={styles.touchPicture}
onPress={() => pickImage()}
>
<Ionicons name="pencil-outline" size={24} color={COLORS.white} />
</TouchableOpacity>
<Image
onChange={onChange}
value={value}
source={{
uri: profilePicture ? profilePicture : images.defaultRounded,
}}
style={[
styles.logo,
styles.profileImage,
{ height: height * 0.19 },
]}
resizeMode={Platform.OS === "android" ? "contain" : "cover"}
/>
</View>
{error && (
<Text
style={{
color: COLORS.red,
alignSelf: "stretch",
fontSize: SIZES.body5,
padding: SIZES.padding - 22,
marginTop: 15,
marginHorizontal: SIZES.padding * 3,
}}
>
{error.message || "❌ Oops, something went wrong!"}
</Text>
)}
</>
)}
/>
);
};
export default CustomImagePicker;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
touchPicture: {
zIndex: 10,
marginBottom: -50,
marginLeft: 100,
resizeMode: "contain",
},
logo: {
width: Platform.OS == "android" ? 155 : 164,
maxWidth: 300,
maxHeight: 200,
},
profileImage: {
marginTop: SIZES.padding * 2,
borderRadius: 100,
},
});

expo ImageManipulator returning [TypeError: undefined is not an object (evaluating '_expo.ImageManipulator.manipulate')]

for some reason
const manipResult = await ImageManipulator.manipulate(
image,
[{ resize: { width: 640, height: 480 } }],
{ format: 'jpg' }
);
returns [TypeError: undefined is not an object (evaluating '_expo.ImageManipulator.manipulate')]
which is weird because it worked one time and then this started to be returned by react native whenever it runs this line of code. so basically what im trying to do is resize the image and then send it to an api but for some reason it won't work and keeps giving this error even though data.uri isn't undefined
whole code
import React, { useState, useEffect, useRef } from 'react';
import { Text, View, StyleSheet, TouchableOpacity, Image } from 'react-native';
import Constants from 'expo-constants';
import { Camera, CameraType } from 'expo-camera';
import * as MediaLibrary from 'expo-media-library';
import { MaterialIcons } from '#expo/vector-icons';
import Button from './src/components/Button';
import axios from 'axios'
import { Buffer } from "buffer";
import * as FS from "expo-file-system";
import { ImageManipulator } from 'expo';
export default function App() {
const [hasCameraPermission, setHasCameraPermission] = useState(null);
const [image, setImage] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.back);
const [flash, setFlash] = useState(Camera.Constants.FlashMode.off);
const cameraRef = useRef(null);
useEffect(() => {
(async () => {
MediaLibrary.requestPermissionsAsync();
const cameraStatus = await Camera.requestCameraPermissionsAsync();
setHasCameraPermission(cameraStatus.status === 'granted');
})();
}, []);
const takePicture = async () => {
if (cameraRef) {
try {
const data = await cameraRef.current.takePictureAsync();
console.log(data);
const manipResult = await ImageManipulator.manipulate(
data.uri,
[{ resize: { width: 640, height: 480 } }],
{ format: 'jpg' }
);
setImage(manipResult0);
} catch (error) {
console.log(error);
}
}
};
uriToBase64 = async (uri) => {
let base64 = await FS.readAsStringAsync(uri, {
encoding: FS.EncodingType.Base64,
});
return base64;
};
toServer = async (mediaFile) => {
const url = "api url";
let response = await FS.uploadAsync(url, mediaFile.uri, {
headers: {
"content-type": "image/jpeg",
},
httpMethod: "POST",
uploadType: FS.FileSystemUploadType.BINARY_CONTENT,
});
console.log(response);
};
const savePicture = async () => {
if(image) {
try {
const asset= await MediaLibrary.createAssetAsync(image);
await toServer({
type:"image",
base64: uriToBase64(image),
uri: image,
});
setImage(null);
console.log('saved successfully');
} catch (error) {
console.log(error);
}
}
};
if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.container}>
{!image ? (
<Camera
style={styles.camera}
type={type}
ref={cameraRef}
flashMode={flash}
>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 30,
}}
>
<Button
title=""
icon="retweet"
onPress={() => {
setType(
type === CameraType.back ? CameraType.front : CameraType.back
);
}}
/>
<Button
onPress={() =>
setFlash(
flash === Camera.Constants.FlashMode.off
? Camera.Constants.FlashMode.on
: Camera.Constants.FlashMode.off
)
}
icon="flash"
color={flash === Camera.Constants.FlashMode.off ? 'gray' : '#fff'}
/>
</View>
</Camera>
) : (
<Image source={{ uri: image }} style={styles.camera} />
)}
<View style={styles.controls}>
{image ? (
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 50,
}}
>
<Button
title="Re-take"
onPress={() => setImage(null)}
icon="retweet"
/>
<Button title="Save" onPress={savePicture} icon="check" />
</View>
) : (
<Button title="Take a picture" onPress={takePicture} icon="camera" />
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#000',
padding: 8,
},
controls: {
flex: 0.5,
},
button: {
height: 40,
borderRadius: 6,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontWeight: 'bold',
fontSize: 16,
color: '#E9730F',
marginLeft: 10,
},
camera: {
flex: 5,
borderRadius: 20,
},
topControls: {
flex: 1,
},
});

Results do not update after a change of state

I have a problem, when I do a search, I get the data from my API, the first time I do a search, everything is fine, all the data is displayed. However, when I do a second search immediately, nothing is updated.
I put in console.log, and I see that I'm getting this data back, yet the display is not updated.
import React, { Component } from "react";
import { SafeAreaView, StyleSheet } from "react-native";
import Search from "./Component/Search";
export default class App extends Component {
render() {
return (
<SafeAreaView style={styles.container}>
<Search />
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
import React from "react";
import { View, TextInput, Button, FlatList, StyleSheet } from "react-native";
import LivreItem from "../Component/LivreItem";
class Search extends React.Component {
constructor(props) {
super(props);
this.inputValue = "";
this.state = { livres: [] };
}
searchBooks = async () => {
const key = "&key=XXXXXXXXXXXXXXXXXXXXXXX";
const url = "https://www.googleapis.com/books/v1/volumes?q=" + this.inputValue + key;
return fetch(url)
.then(response => response.json())
.catch(e => {
console.log("Une erreur s'est produite");
console.log(e);
});
};
getBooks = () => {
if (this.inputValue.length > 0) {
this.searchBooks()
.then(data => this.setState({ livres: data.items }))
.catch(reject => console.log(reject));
}
};
changeText = text => {
this.inputValue = text;
};
render() {
return (
<View style={styles.header_container}>
<View style={styles.sub_container}>
<TextInput
onChangeText={text => this.changeText(text)}
style={styles.input}
placeholder="Ex: Harry Potter"
/>
<Button
style={styles.button}
title="Rechercher"
onPress={() => this.getBooks()}
/>
</View>
<FlatList
style={styles.list}
data={this.state.livres}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <LivreItem livre={item.volumeInfo} />}
/>
</View>
);
}
}
const styles = StyleSheet.create({
sub_container: {
justifyContent: "space-between",
flexDirection: "row",
marginTop: 30,
paddingRight: 10,
paddingLeft: 10
},
header_container: {
flex: 1,
flexDirection: "column",
padding: 10
},
input: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: "#d6d7da",
width: 150,
paddingLeft: 5
},
button: {
borderRadius: 4
},
list: {
paddingLeft: 15,
paddingRight: 15
}
});
export default Search;
import React from "react";
import { View, StyleSheet, Image, Text } from "react-native";
class LivreItem extends React.Component {
constructor(props) {
super(props);
this.state = { livre: this.props.livre};
this.description =
this.state.livre.description === null || this.state.livre.description === undefined
? "Pas de description disponible"
: this.state.livre.description;
this.img = this.state.livre.imageLinks;
this.image =
this.img === undefined ||
this.img.smallThumbnail === undefined ||
this.img.smallThumbnail === null
? null
: this.state.livre.imageLinks.smallThumbnail;
}
render() {
return (
<View style={styles.content}>
<View>
<Image style={styles.image} source={{ uri: this.image }} />
<Image style={styles.image} source={this.image} />
</View>
<View style={styles.content_container}>
<Text style={styles.titre}>{this.state.livre.title}</Text>
<Text style={styles.description} numberOfLines={4}>
{this.description}
</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
content: {
height: 125,
flexDirection: "row",
marginTop: 15
},
content_container: {
flexDirection: "column",
flexShrink: 1,
marginLeft: 10
},
image: {
width: 100,
height: 100
},
titre: {
fontWeight: "bold",
flexWrap: "wrap"
},
description: {
flexWrap: "wrap"
}
});
export default LivreItem;
Thanks.
Configure the prop extraData in Flatlist component ala:
<FlatList
extraData={this.state.livres}
/>
Pass a boolean value to the FlatList extraData.
<FlatList
extraData={this.state.refresh}
/>