I am using React Native and Expo to make a mobile application in which one of the features is that the user can upload a form that includes an image. I am using expo ImagePicker: https://docs.expo.io/versions/latest/sdk/imagepicker/ to select the image or take a picture with a camera and then using Expo Media Library https://docs.expo.io/versions/latest/sdk/media-library/ to save the image locally to a folder on my phone emulator labelled "AppPhotos". I am using BlueStack for the phone emulator. The images are successfully being saved to the AppPhotos folder in the Media Manager in BlueHost. Here is the code for entering the form and saving the photo:
Form Submit
import React, { useEffect, useState } from 'react';import { Button, View, Text, TextInput, Picker } from 'react-native';import { styles } from './styles.js';import * as ImagePicker from 'expo-image-picker';import * as MediaLibrary from 'expo-media-library';const FormSubmit = ({navigation, route}) => { const [Name, onChangeName] = useState(null); const [Phone, onChangePhone] = useState(null); const [Email, onChangeEmail] = useState(null); const [selectedValue, setSelectedValue] = useState(null); const [MileMarker, onChangeMileMarker] = useState(null); const [Comments, onChangeComments] = useState(null); const [RoadName, onChangeRoadName] = useState(null); const [image, setImage] = useState(null); const [hasPermission, setHasPermission] = useState(null); const counties = ["Barbour", "Berkeley", "Boone", "Braxton", "Brooke", "Cabell", "Calhoun", "Clay", "Doddridge", "Fayette", "Gilmer", "Grant", "Greenbrier", "Hampshire", "Hancock", "Hardy","Harrison", "Jackson", "Jefferson", "Kanawha", "Lewis", "Lincoln", "Logan", "Marion", "Marshall", "Mason", "Mercer", "Mineral", "Mingo", "Monongalia", "Monroe", "Morgan", "McDowell","Nicholas", "Ohio", "Pendleton", "Pleasants", "Pocahontas", "Preston", "Putnam", "Raleigh", "Randolph", "Ritchie", "Roane", "Summers", "Taylor", "Tucker", "Tyler", "Upshur", "Wayne", "Webster", "Wetzel", "Wirt", "Wood", "Wyoming"] const pickImage = async () => { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.All, allowsEditing: true, aspect: [4, 3], quality: 1, }); console.log(result); if (!result.cancelled) { setImage(result.uri); } }; const takeImage = async () => { let result = await ImagePicker.launchCameraAsync({ mediaTypes: ImagePicker.MediaTypeOptions.All, allowsEditing: true, aspect: [4, 3], quality: 1, }); console.log(result); if (!result.cancelled) { setImage(result.uri); } }; //This needs to happen when I press the button I think but it doesnt really seem to work if I dp. useEffect(() => { (async () => { if (Platform.OS !== 'web') { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== 'granted') { alert('Sorry, we need camera roll permissions to make this work!'); } } const {status} = await ImagePicker.requestCameraPermissionsAsync(); setHasPermission(status === 'granted'); })(); }, []); if (hasPermission === null) { return <View />; } if (hasPermission === false) { return <Text>No access to camera</Text>; } //POST a new form to the database const postForm = async () =>{ console.log("Image URI: " + image); fetch('http://10.0.2.2:5000/forms', { method: 'POST', headers: {'Accept': 'application/json','Content-Type': 'application/json', }, body: JSON.stringify({ Name: Name, Phone: Phone, Email: Email, County: selectedValue, RoadName: RoadName, MileMarker: MileMarker, Comments: Comments, Path: image }) }).then(response =>{ if(response.ok){ return response.json(); } }).then(data => console.log(data)); } const SaveToPhone = async (item) => { // Remember, here item is a file uri which looks like this. file://.. const permission = await MediaLibrary.requestPermissionsAsync(); if (permission.granted) { try { const asset = await MediaLibrary.createAssetAsync(item); MediaLibrary.createAlbumAsync('AppPhotos', asset, false) .then(() => { console.log('File Saved Successfully!'); }) .catch(() => { console.log('Error In Saving File!'); }); } catch (error) { console.log(error); } } else { console.log('Need Storage permission to save file'); } }; return (<View style={styles.container}><Text>Submit a complaint form.</Text><View style={styles.rowContainer}><Text style={styles.myText}>Name</Text><TextInput style={styles.input} onChangeText={onChangeName} value={Name} /></View><View style={styles.rowContainer}><Text style={styles.myText}>Phone</Text><TextInput style={styles.input} onChangeText={onChangePhone} value={Phone} /></View><View style={styles.rowContainer}><Text style={styles.myText}>Email</Text><TextInput style={styles.input} onChangeText={onChangeEmail} value={Email} /></View><View style={styles.rowContainer}><Picker style={styles.charPicker} mode="dropdown" selectedValue={selectedValue} onValueChange={(itemValue, itemIndex) => setSelectedValue(itemValue)}> {counties.map((item, index) => { return (<Picker.Item label={item} value={item} key={index}/>) })}</Picker></View><View style={styles.rowContainer}><Text style={styles.myText}>Mile Marker</Text><TextInput style={styles.input} onChangeText={onChangeMileMarker} value={MileMarker} /></View><View style={styles.rowContainer}><Text style={styles.myText}>Road Name</Text><TextInput style={styles.input} onChangeText={onChangeRoadName} value={RoadName} /></View><View style={styles.rowContainer}><Text style={styles.myText}>Comments</Text><TextInput style={styles.textArea} numberOfLines={10} multiline={true} onChangeText={onChangeComments} value={Comments} /></View><Button title="Pick an image from camera roll" onPress={pickImage} /><Button title="Take a picture" onPress={takeImage} /><Button style = {styles.button} title="Submit" onPress={() => { SaveToPhone(image); postForm(); }} color="#19AC52" /></View> );}export default FormSubmit;
These forms (including the image URI) are saved to an SQL database. This is an example of one of the image uri's that is stored as "Path" :
file:///data/user/0/host.exp.exponent/cache/ExperienceData/UNVERIFIED-10.69.64.21-DOH_Mobile_V1/ImagePicker/582fb57f-2ff9-477e-886b-de26e0f03c4b.jpg
However, when I try to loop over and display the existing forms I can not get the images. If you notice, for each card I can simply use item.Name
or item.Phone
which works fine. However for some reason this does not work with the image URI. Here is the code for that:
Forms Screen
import React, { useEffect, useState } from 'react';import { View, Text, FlatList, ScrollView, Image } from 'react-native';import { Card } from 'react-native-elements'import { styles } from './styles.js';import * as ImagePicker from 'expo-image-picker';const FormsScreen = ({navigation, route}) => { const [formsArray, setFormsArray] = useState([]); //Fetch all users from database useEffect(() =>{ fetch('http://10.0.2.2:5000/forms').then(response =>{ if(response.ok){ return response.json(); } }).then(data => setFormsArray(data)); }, []); for(var i = 0; i<formsArray.length; i++){ console.log(formsArray[1].Path); } return (<FlatList keyExtractor={(item) => item.ID.toString() } style = {styles.List} data={formsArray} renderItem={({item}) => (<Card><Card.Title>{item.ID}</Card.Title><Card.Divider/><View style={styles.Container}><Text>{item.Comments}</Text><Image source={require(item.Path)} /><Text>{item.RoadName}</Text></View><View style={styles.ListContainer}><Text style={styles.LabelText}>Name</Text><Text style={styles.LabelText}>Phone</Text><Text style={styles.LabelText}>Email</Text></View><View style={styles.ListContainer}><Text style={styles.CardText}>{item.Name}</Text><Text style={styles.CardText}>{item.Phone}</Text><Text style={styles.CardText}>{item.Email}</Text></View></Card> )} /> );}export default FormsScreen;
I have some hunches about why this doesn't work. First of all, with using just the image URI there is no way for the Forms Screen to know what folder the images are stored in or even that they are stored locally on the device. So how can I direct the code to look for that image URI in the emulated phones Media Manage > AppPhotos folder? Also, once stored inside the AppPhotos folder the name of the image is simply everything after /ImagePicker/
.
How can I remedy this issue? Am I approaching this incorrectly?