How to preload an image from local in react native with Expo - react-native

I'm doing a project but i'm stuck when loading a background image.
<Image
source={Images.welcomeBg}
style={styles.container}
>
...
</Image>
But Image spends 1-2 second to load it
I'm doing follow this link
It still doesn't work right now.
Plz help me fix this bug

you can do this
make new called cachedAssetAsync.js (sure you can choose whatever name you like)
import { Asset, Font } from 'expo';
export default function cacheAssetsAsync({
images = [],
fonts = [],
videos = [],
}) {
return Promise.all([
...cacheImages(images),
...cacheFonts(fonts),
...cacheVideos(videos),
]);
}
function cacheImages(images) {
return images.map(image => Asset.fromModule(image).downloadAsync());
}
function cacheVideos(videos) {
return videos.map(video => Asset.fromModule(video).downloadAsync());
}
function cacheFonts(fonts) {
return fonts.map(font => Font.loadAsync(font));
}
then in App.js or whatever your root component you use, can do like this
async _loadAssetsAsync() {
try {
await cacheAssetsAsync({
images: [require('./assets/images/exponent-icon.png')],
fonts: [
{ 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf') },
MaterialIcons.font,
],
videos: [require('./assets/videos/ace.mp4')],
});
} catch (e) {
console.log({ e });
} finally {
this.setState({ appIsReady: true });
}
}
you can do the logic when appIsReady is false shows loading screen then when appIsReady is true show the actual screen. And sure you can do this in only one file.
expo doc: https://docs.expo.io/versions/latest/guides/preloading-and-caching-assets.html

Related

Font.loadAsync with expo SplashScreen won't work in react native

I updated to expo SDK 45. I used to load open-sans like so:
const fetchFonts = () => {
return Font.loadAsync({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
};
export default function App() {
const [dataLoaded, setDataLoaded] = useState(false);
if (!dataLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setDataLoaded(true)}
onError={(err) => console.log(err)}
/>
);
}
Problem is that AppLoading is no longer supported. Instead one has to use SplashScreen now. I followed the example here. This is my code:
import * as Font from "expo-font";
import * as SplashScreen from 'expo-splash-screen';
SplashScreen.preventAutoHideAsync();
export default function App() {
/* Preload stuff */
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await Font.loadAsync({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
} catch (e) {
console.warn(e);
} finally {
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// This tells the splash screen to hide immediately! If we call this after
// `setAppIsReady`, then we may see a blank screen while the app is
// loading its initial state and rendering its first pixels. So instead,
// we hide the splash screen once we know the root view has already
// performed layout.
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
Now my app won't load - I'll only see the the splash screen. I can make the app work if I comment out SplashScreen.preventAutoHideAsync(); and onLayoutRootView. I then get the message that "fontfamily opensans is not a system font". Sometimes it also actually does load the font and it works (every other try)
Any thoughts?

React Native Expo “AppLoading threw an unexpected error when loading” error

I have a React Native project with Expo, I installed Expo client on my Android phone. It used to work well so far. But even though I didn't change any code, I now get the following error when I scan the QR code from my phone. This error is shown on terminal and phone screen keeps blank.
import React from 'react';
import { Image } from 'react-native';
import { AppLoading } from 'expo';
import { Asset } from 'expo-asset';
import { Block, GalioProvider } from 'galio-framework';
import Screens from './navigation/Screens';
import { Images, articles, ditsTheme } from './constants';
// cache app images
const assetImages = [
Images.Onboarding,
Images.LogoOnboarding,
Images.Logo,
Images.Pro,
Images.DITSLogo,
Images.iOSLogo,
Images.androidLogo
];
// cache product images
articles.map(article => assetImages.push(article.image));
function cacheImages(images) {
return images.map(image => {
if (typeof image === 'string') {
return Image.prefetch(image);
} else {
return Asset.fromModule(image).downloadAsync();
}
});
}
export default class App extends React.Component {
state = {
isLoadingComplete: false,
}
render() {
if(!this.state.isLoadingComplete) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<GalioProvider theme={ditsTheme}>
<Block flex>
<Screens />
</Block>
</GalioProvider>
);
}
}
_loadResourcesAsync = async () => {
return Promise.all([
...cacheImages(assetImages),
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
How can I solve this error?
In your _handleLoadingError method, you're using warn. While you should be using console.warn. This is what's breaking your app.
I was getting this error, the problem was with AppLoading. The issue turned out to be incorrect function name that I was calling from <AppLoading .. />
Note below, I was using Font.loadAsync (with missing c), fixing it made the font load correctly.
const getFonts = () => Font.loadAsyn({
'nanitu-regular' : require('./assets/fonts/EastSeaDokdo-Regular.ttf'),
'nanitu-bold' : require('./assets/fonts/NanumBrushScript-Regular.ttf')
})
;
You should add onError to AppLoading. As a like:
<AppLoading startAsync={getFonts} onFinish={() => {
setFontLoaded(true)
}} onError={console.warn} />

App stop working when Image Picker is opened in React Native

I am developing a React Native application using React Native. I am using react native image picker library, https://www.npmjs.com/package/react-native-imagepicker to pick up the images from the Gallery. But when I opened the image picker, my app stopped working and exited.
This is my code
import React from "react";
import { CameraRoll, View, Text, Button, Alert, Image } from "react-native";
import ImagePicker from "react-native-image-picker";
// More info on all the options is below in the API Reference... just some common use cases shown here
const options = {
title: "Select Avatar",
customButtons: [{ name: "fb", title: "Choose Photo from Facebook" }],
storageOptions: {
skipBackup: true,
path: "images"
}
};
class Gallery extends React.Component {
constructor(props) {
super(props);
this.state = {
url:"https://www.designevo.com/res/templates/thumb_small/terrible-black-bat-icon.png",
avatarSource: null
};
}
saveToCameraRoll = () => {
let { url } = this.state;
};
_handlePickImageButton = () => {
ImagePicker.showImagePicker(options, response => {
console.log("Response = ", response);
if (response.didCancel) {
Alert.alert("User cancelled image picker")
} else if (response.error) {
//console.log("ImagePicker Error: ", response.error);
Alert.alert("ImagePicker Error:");
} else if (response.customButton) {
//console.log("User tapped custom button: ", response.customButton);
Alert.alert("Custom button");
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
this.setState({
avatarSource: source
});
}
});
};
render() {
return (
<View>
<Button
onPress={() => {
this._handlePickImageButton();
}}
title="Pick a image"
>
Pick image
</Button>
<Image source={this.state.avatarSource} />
</View>
);
}
}
export default Gallery;
What is wrong with my code? Also, I did not get any error info in the console as in the screenshot attached below.
I tried, opening in this way too
ImagePicker.launchImageLibrary(options, (response) => {
//nothing implemented yet
});
It just stopped working.
I added the following permission in the plist as well:
I tried this too
const options = {
noData: true
};
ImagePicker.launchImageLibrary(options, (response) => {
});
I found the issue. The problem was in the plist. When I was adding the permissions, I just copy-pasted from a post. Might be something was wrong with it. When I typed in the permissions in the XCode, I saw the suggestion box, so I just clicked on the suggestion box and added the description for each permission as below.
As you can see in the above screenshot, the String value in the Type column is grayed out and cannot be changed. In the screenshot attached in the question, those values can be changed. That is the difference.

undefined is not an object (evaluating '_expo.Permission.askAsync')

i don't know what's the problem exactly but when i click on the button to choose image that erreur fire in the console
here's my code
_checkPermissions = async () => {
try {
const { status } = await Permission.askAsync(Permission.CAMERA);
this.setState({ camera: status });
const { statusRoll } = await Permission.askAsync(Permission.CAMERA_ROLL);
this.setState({ cameraRoll: statusRoll });
} catch (err) {
console.log(err);
}
};
findNewImage = async () => {
try {
this._checkPermissions();
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: "Images",
allowsEditing: true,
quality: 1
});
if (!result.cancelled) {
this.setState({
image: result.uri
});
} else {
console.log("cancel");
}
} catch (err) {
// console.log(err);
}
};
to me what solved it was importing the permissions and imagePicker like this:
import * as Permissions from 'expo-permissions';
import * as ImagePicker from 'expo-image-picker';
instead of this:
import Permissions from 'expo-permissions';
import ImagePicker from 'expo-image-picker';
And that's basically because there is no default export
It is getAsync(), not askAsync()
https://docs.expo.io/versions/latest/sdk/permissions/
I know I'm a little late to the party, but I feel it's important to show a way that is currently working (as of 2022) and also askAsync is deprecated ...
Getting image from (native) CAMERA
TL;DR: Even though we want "camera", we will actually use expo-image-picker FOR THE CAMERA (yes, you read right!)
I REPEAT: DO NOT USE expo-camera FOR CAMERA!
REMEMBER: USE ImagePickerExpo.requestCameraPermissionsAsync()AND ImagePickerExpo.launchCameraAsync() NOT Camera....!
So install it first: expo install expo-image-picker
Then import everything, from it under 1 alias, I like to use ImagePickerExpo because ImagePicker itself is confusing since it can mean more libraries, + import all needed for this code - you can replace Button with any other button/pressable that supports onPress (to use react-native-elements, you need to install it with yarn add react-native-elements)
Create displaying component
Create a state & setter to save current image source
Create a function that requests the permissions and opens the camera
Return the coponent with button binding onPress on function from 5. and Image that is displayed from the state from 4. but only when available.
working & tested(so far on android in expo go) code:
import React, { useState } from 'react';
import { View, Image, Alert, StyleSheet } from 'react-native';
import { Button } from 'react-native-elements';
import * as ImagePickerExpo from 'expo-image-picker';
const MyCameraComponent = () => {
const [selectedImage, setSelectedImage] = useState(null);
const openCameraWithPermission = async () => {
let permissionResult = await ImagePickerExpo.requestCameraPermissionsAsync();
if (permissionResult.granted === false) {
Alert.alert("For this to work app needs camera roll permissions...");
return;
}
let cameraResult = await ImagePickerExpo.launchCameraAsync({
// ...
});
console.log(cameraResult);
if (cameraResult.cancelled === true) {
return;
}
setSelectedImage({ localUri: cameraResult.uri });
};
return (
<View>
<Button title='Take a photo' onPress={openCameraWithPermission}></Button>
{(selectedImage !== null) && <Image
source={{ uri: selectedImage.localUri }}
style={styles.thumbnail}
/>}
</View>
);
}
const styles = StyleSheet.create({
thumbnail: {
width: 300,
height: 300,
resizeMode: "contain"
}
});
export default MyCameraComponent;
Note that I had to style the Image for it to display, it didn't display to me without proper styling which I find misleading, but I guess that's the react native way...
BTW: This also works in Android emulator (besides expo go in real Android device)
It also works on snack on desktop but only when you choose android (or Web) - https://snack.expo.dev/#jave.web/expo-camera-from-expo-image-picker
Getting image from (native) gallery (not camera)
In case you're wondering how to do the same for gallery, the code is basically the same, you just need a different callback function for the button that uses requestMediaLibraryPermissionsAsync / launchImageLibraryAsync instead of the camera ones.
let openImagePickerAsync = async () => {
let permissionResult = await ImagePickerExpo.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
Alert.alert("For this to work app needs media library/gallery permissions...");
return;
}
let pickerResult = await ImagePickerExpo.launchImageLibraryAsync({
presentationStyle: 0, // without this iOS was crashing
});
console.log(pickerResult);
if (pickerResult.cancelled === true) {
return;
}
setSelectedImage({ localUri: pickerResult.uri });
}

react-native-camera barcode scanner freezes, because it scans too fast

I am trying to use the barcode scanner from react-native-camera. First, off it scans a QR-code and extracts a String, after that it navigates to the next Screen with react-navigation. In the second screen, it makes an API-call.
Now if I go back to the scanner screen, de QR-code will be scanned immediately. That's where I run into an error and the scanner freezes. I usually get this error:
Can't call setState (or forceUpdate) on an unmounted component
I think it's because my componentWillUnmount cleanup doesn't work properly or fast enough, but I already cancel the axios request.
requestCode = (code) => {
if (cancel != undefined) {
cancel();
}
axios.get(API_URI + code, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
}).then(response => {
console.log(response)
//checks if code was already called
this.checkUsed(response.data)
})
.catch(error => {
this.setState({ isValid: false })
});
}
componentWillUnmount() {
cancel();
}
Maybe I could mount the camera-scanner a little bit later so it doesn't scan this fast or is it maybe even an error with React Navigation?
You can use a flag to control.
class QR extends Component {
constructor(props) {
super(props)
this.state = {
scanable: true
}
this.cameraAttrs = {
ref: ref => {
this.camera = ref
},
style: styles.preview,
type: RNCamera.Constants.Type.back,
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
onBarCodeRead: ({ data }) => {
this.callback(data)
}
}
}
componentWillMount() {
this._mounted = true
}
componentWillUnmount() {
this._mounted = false
}
callback(text) {
if (!this.state.scanable) {
return
}
console.log(text)
this.setState({ scanable: false })
setTimeout(() => {
if (this._mounted) {
this.setState({ scanable: true })
}
}, 1000) // 1s cooldown
}
render() {
return (
<View style={styles.container}>
<RNCamera
{...this.cameraAttrs}
>
</RNCamera>
</View>
)
}
}