I am using Expo to build a React Native app. I want users to be able to save a profile picture. I have been trying this for months. I use Expo image picker to do this. However, the file I am getting back is in the local system. When I try to log in to another phone the image is not showing up, probably because it is restricted only to the phone the image was being fetched from. I read an article about removing file:// on iOS but it doesn't work anyway. In the backend, I am using GraphQL and I am just storing the photoUrl as a string in a MongoDB database. I mean, there are a lot of React Native apps out there so there must be a simple solution to this.
Code:
useEffect(() => {
(async () => {
if (Platform.OS !== 'web') {
const {
status,
} = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert(
'Vi behöver din profilbild för att göra det lättare för andra användare att veta vem du är'
);
}
}
})();
}, []);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.cancelled && Platform.OS === 'ios') {
const photo = result?.uri.replace('file://', '/private');
setPhotoUrl(photo);
}
};
CodePudding user response:
You are setting the local image uri for upload method, so when you try to fetch it back it cannot because the uri you are uploading to db is the local uri.
You have to use a blob storage and configure your backend...
Steps:
- get file name from the local uri
- upload the image to blob storage
- save blob url in yout db as 'string' (eg: https://yourblobstorage/profilepicture/filename.png)
- best idea is to change the file name to userid or some kind of unique id.
- so every time you fetch this url from your blob for this user
- and you do the same for change profilepicture (so the user profilepicture name is always his user id, which makes it simple for you and saves some space XD)
My implementation:
Image picker method:
const addCameraImage = async () => {
setOverlayVisible(false)
const { granted } = await ImagePicker.requestCameraPermissionsAsync()
if (granted === true) {
let image = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
})
await uploadImage(image)
} else {
setPermissionOverlayVisible(true)
}
}
upload image method:
const uploadImage = async (image: ImagePicker.ImagePickerResult) => {
if (image.cancelled === false) {
setLocalImage(image.uri)
const filename = image.uri.split('/').pop();
const file = new ReactNativeFile({
uri: image.uri,
name: filename,
type: `image/${filename.split('.').pop()}`
});
try {
await updateProfilePictureMutation({
variables: {
id: data?.user?.id,
profilePicture: file
},
update: (cache, { data }) => {
try {
const getUserByIdCachedData = cache.readQuery<GetUserByIdQuery>({ query: GetUserByIdDocument, variables: { id: userId } })
if (!getUserByIdCachedData) return
const newData = {
...getUserByIdCachedData,
user: {
...getUserByIdCachedData.user,
profilePicture: data.updateUser.profilePicture
}
}
cache.writeQuery({ query: GetUserByIdDocument, variables: { id: userId }, data: newData })
} catch (e) {
console.error(e)
}
}
})
} catch (e) {
}
setLocalImage(null)
}
}
open a modal which asks user persmission to open settings to enable permission if they have already denied before and tries to take a picture:
const openSettings = () => {
if (Platform.OS === 'ios') {
Linking.openURL('app-settings:')
setPermissionOverlayVisible(false)
}
if (Platform.OS === 'android') {
const pkg = Constants.manifest.releaseChannel ? Constants.manifest.android.package : 'host.exp.exponent'
IntentLauncher.startActivityAsync(
IntentLauncher.ACTION_APPLICATION_DETAILS_SETTINGS,
{ data: 'package:' pkg }
)
setPermissionOverlayVisible(false)
}
}
Now here I set the local image as soon as they take a picture so they dont have to wait for the time you upload the picture to blob, once its uploaded, i fetch from blob
const getAvatarImage = () => {
if (updateProfilePictureLoading) return null
if (localImage) {
return { uri: localImage }
}
if (data?.user?.profilePicture) {
return { uri: data?.user?.profilePicture }
}
return null
}
I hope this helps you understand a little bit more, I was stuggling with this so much, but later I realized its quite simple.
I have also made mutation and query for my upload and fetching from blob on backend.
Cheers.