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

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 });
}

Related

Expo React Native download a pdf file from server and save it in the local file system

I am having a hard time understanding the process of downloading a file pdf or excel from let's say a nodejs server in ReactNative using Expo.
So far I have gone through a bunch of blog posts/resources on the web but unfortunately I didn't find any solution for this. Most of the blogs are outdated or they don't have any clear implementation for it.
Here are a few approach that I came across.
using rn-fetch-blob but this does not work with expo as per my findings and each time I tried to integrate it with Expo, I ended up with error.
The second approach to use expo's FileSharing approach, which kind of works but only if you have a predefined image or pdf file.
import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import { StyleSheet, Text, View, Button, Alert } from "react-native";
import * as FileSystem from "expo-file-system";
import * as Sharing from "expo-sharing";
export default function App() {
const [downloadProgress, setDownloadProgress] = useState(0);
const [document, setDocument] = useState(null);
async function openShareDialogAsync() {
if (!(await Sharing.isAvailableAsync())) {
alert(`Uh oh, sharing isn't available on your platform`);
return;
}
Sharing.shareAsync(document);
}
async function handleDownload() {
const callback = (downloadProgress) => {
const progress =
downloadProgress.totalBytesWritten /
downloadProgress.totalBytesExpectedToWrite;
setDownloadProgress(progress * 100);
};
let url =
"https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf";
const downloadResumable = FileSystem.createDownloadResumable(
url,
FileSystem.documentDirectory + "a.pdf",
{},
callback
);
try {
const { uri } = await downloadResumable.downloadAsync();
console.log("Finished downloading to ", uri);
setDocument(uri);
} catch (e) {
console.error(e);
}
}
useEffect(() => {
handleDownload();
}, []);
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>Welcome Saroj!</Text>
<Button title="Share" onPress={openShareDialogAsync} />
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
This implementation kind of works. I am able to share the pdf but the issue is the pdf link is not generated on the fly, it is something already being generated previosuly. I expect something like we can call an endpoint to a server and the server would either send base64 or binary data that we can use to make the pdf download to the local storage of the device.
I am open to suggestions and would really appreciate your inputs.
Use Blob and FileReader to download any binary data. Something like this:
const response = await fetch('<link>');
const data = await response.blob()
const reader = new FileReader();
reader.onload = async () => {
const fileUri = FileSystem.documentDirectory + "file";
await FileSystem.writeAsStringAsync(
fileUri, reader.result.split(',')[1],
{ encoding: FileSystem.EncodingType.Base64 }
);
};
reader.readAsDataURL(data);

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 CLI Import .ttf Fonts

Trying to import .ttf for font in expo cli.
I also have splash screen. I wanna show the splash screen until the font loads.
Font: Josefin Sans.
"expo": "~45.0.0"
I took reference from following links but nothing works:
Using Custom Fonts: https://docs.expo.dev/guides/using-custom-fonts/
Splash Screen: https://docs.expo.dev/versions/latest/sdk/splash-screen/
Code (App.js)
import { useState, useEffect, useCallback } from "react";
import { Text } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import Header from "./components/Header.component";
import styles from "./styles/appStyle";
import * as Font from "expo-font";
import * as SplashScreen from "expo-splash-screen";
const App = () => {
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// Pre-load fonts
await Font.loadAsync({
"JosefinSans-Regular": require("./assets/fonts/JosefinSans-Regular.ttf"),
});
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
await new Promise((resolve) => setTimeout(resolve, 2000));
} catch (e) {
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
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;
}
return (
<>
<SafeAreaView style={styles.container} onLayout={onLayoutRootView}>
<Header />
<Text>Hello World!</Text>
<StatusBar style="light" backgroundColor="#05060B" />
</SafeAreaView>
</>
);
};
export default App;
Error
Android Bundling failed 12ms
Unable to resolve module ./assets/fonts/JosefinSans-Regular.ttf from C:\Users\user\Desktop\app\App.js:
None of these files exist:
* JosefinSans-Regular.ttf
* assets\fonts\JosefinSans-Regular.ttf\index(.native|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx|.android.js|.native.js|.js|.android.jsx|.native.jsx|.jsx|.android.json|.native.json|.json)
18 | // Pre-load fonts
19 | await Font.loadAsync({
> 20 | "JosefinSans-Regular": require("./assets/fonts/JosefinSans-Regular.ttf"),
| ^
21 | });
22 | // Artificially delay for two seconds to simulate a slow loading
23 | // experience. Please remove this if you copy and paste the code!
File Structure:
Snap.png
Answer
expo install #expo-google-fonts/josefin-sans expo-font
And the code looks like this.
import { useState, useEffect, useCallback } from "react";
import { Text } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import Header from "./components/Header.component";
import styles from "./styles/appStyle";
import * as Font from "expo-font";
import * as SplashScreen from "expo-splash-screen";
import {
useFonts,
JosefinSans_100Thin,
JosefinSans_200ExtraLight,
JosefinSans_300Light,
JosefinSans_400Regular,
JosefinSans_500Medium,
JosefinSans_600SemiBold,
JosefinSans_700Bold,
JosefinSans_100Thin_Italic,
JosefinSans_200ExtraLight_Italic,
JosefinSans_300Light_Italic,
JosefinSans_400Regular_Italic,
JosefinSans_500Medium_Italic,
JosefinSans_600SemiBold_Italic,
JosefinSans_700Bold_Italic,
} from "#expo-google-fonts/josefin-sans";
const App = () => {
const [appIsReady, setAppIsReady] = useState(false);
let [fontsLoaded] = useFonts({
JosefinSans_100Thin,
JosefinSans_200ExtraLight,
JosefinSans_300Light,
JosefinSans_400Regular,
JosefinSans_500Medium,
JosefinSans_600SemiBold,
JosefinSans_700Bold,
JosefinSans_100Thin_Italic,
JosefinSans_200ExtraLight_Italic,
JosefinSans_300Light_Italic,
JosefinSans_400Regular_Italic,
JosefinSans_500Medium_Italic,
JosefinSans_600SemiBold_Italic,
JosefinSans_700Bold_Italic,
});
const prepare = async () => {
try {
// Pre-load fonts
await Font.loadAsync(fontsLoaded)
.then(() => {
setAppIsReady(true);
})
.catch((err) => {});
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
// await new Promise((resolve) => setTimeout(resolve, 2000));
} catch (e) {}
};
useEffect(() => {
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;
}
return (
<>
<SafeAreaView style={styles.container} onLayout={onLayoutRootView}>
<Header />
<Text>Hello World!</Text>
<StatusBar style="light" backgroundColor="#05060B" />
</SafeAreaView>
</>
);
};
export default App;

How to write a jest test for opening of an URL in react native?

I'm trying to write a test case for testing URL in my react native app this is my mock
import { Linking } from "react-native";
jest.mock('react-native/Libraries/Linking/Linking', () => {
return {
openURL: jest.fn()
}
})
Linking.openURL.mockImplementation(() => true)
and this is my test
test('open google url',async ()=>{
expect(Linking.openURL()).toHaveBeenCalled('https://www.google.com/')
})
but I get this error what should I do?
Name the mock function in a constant and then test if that function has been called. Here's how you would set up:
import * as ReactNative from "react-native";
const mockOpenURL = jest.fn();
jest.spyOn(ReactNative, 'Linking').mockImplementation(() => {
return {
openURL: mockOpenURL,
}
});
and then you can test this way (my example uses react-testing-library, but you can use whatever). Note you should use toHaveBeenCalledWith(...) instead of toHaveBeenCalled(...)
test('open google url', async () => {
// I assume you're rendering the screen here and pressing the button in your test
// example code below
const { getByTestId } = render(<ScreenToTest />);
await act(async () => {
await fireEvent.press(getByTestId('TestButton'));
});
expect(mockOpenURL.toHaveBeenCalledWith('https://www.google.com/'));
});
If I understoof your question then you can use react-native-webview.
import WebView from 'react-native-webview';
export const WebView: React.FC<Props> = ({route}) => {
const {url} = route.params;
<WebView
source={{uri: url}}
/>
);
};
This is how I use my webview screen for any url I need to open (like terms and conditions, etc...)

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.