Expo - Access to all photos is required to do this operation - react-native

I have an application built with expo and when a user download a picture on iOS, I'm getting this error on bugsnag:
Access to all photos is required to do this operation.
I'm not myself an iPhone user. I could replicate this with someone else iPhone, got the error but nothing apparent on the iPhone's user.
I realized later that I forgot to add this to app.config.json
export default {
...
"plugins": [
[
"expo-media-library",
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
"savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos."
}
]
]
}
After adding it, it seems to have fixed the issue on the testflight (probably because the user had to uninstall-reinstall the app).
However, after deploying the app in the store, the error keeps happening. I suppose it should have re-asking for permissions ? (If so, I have no idea how)
Here's how I register the permissions and download the picture
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system';
import logger from '~application/utility/logger';
const albumName = "MyApplicationName";
const download = async (uri, fileName, headers, callback) => {
registerLibraryPermissions()
.then((granted) => granted ? downloadFile(uri, fileName, headers, callback) : null)
};
const downloadFile = async (uri, fileName, headers, callback) => {
if (Object.keys(headers).length === 0) return null;
try {
return FileSystem.createDownloadResumable(uri, FileSystem.documentDirectory + fileName, {headers: headers}, callback)
.downloadAsync()
.then(({uri}) => moveToGallery(uri))
.catch(error => logger.log("Error downloading the file", error));
} catch (error) {
logger.log("Error while downloading the photo", error)
}
}
const moveToGallery = async (localUri) => {
try {
const asset = await MediaLibrary.createAssetAsync(localUri);
return await MediaLibrary.getAlbumAsync(albumName).then(album => {
if (album !== null) {
return MediaLibrary.addAssetsToAlbumAsync([asset], album, false)
.catch((error) => logger.log('Error while adding photo', error));
}
return MediaLibrary.createAlbumAsync(albumName, asset, false)
.catch((error) => logger.log('Error while saving photo', error));
});
} catch (error) {
logger.log('Error while saving photo', error)
}
}
const registerLibraryPermissions = async () => {
try {
const {status: existingStatus} = await MediaLibrary.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const {status} = await MediaLibrary.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert("L'accès à la média libairie n'a pas pu être activé sur votre téléphone");
return false;
}
return true;
} catch (error) {
logger.log('Error getting the permissions for media library', error);
}
};
export default {
download,
};
Am I missing something? Thanks

Related

Setting data from firebase with useState is returning undefined

I am trying to set data for verification purposes. I do set the data then get undefined which is disturbing to me, I've tried to parse it in different shapes, I've used useCallback hook and without any real benefit
const getUserPhone = useCallback(async () => {
console.log('user phone is requested');
await firebase
.database()
.ref(`users/${login.uid}`)
.once(
'value',
async (data) => {
if (data.exists()) {
console.log('found');
await setUserData(data.toJSON());
console.log('data has been set');
} else {
Alert.alert('User not found');
return;
}
},
// I've tried .get() from firebase and
//.then(async (data: IUser) => {await setUserData(data.toJSON()})
// It does the same.
)
.catch((error) => {
return console.error(error);
});
}, [login.uid]);
const handleVerification = useCallback(async () => {
if (alreadyRequested) {
return;
}
await getUserPhone();
try {
console.log(userData); // undefined
if (!userData?.phoneNumber) {
console.log('no phone number is here');
return;
}
...
} catch ...
}, [alreadyRequested, getUserData, userData?.phoneNumber])
Output:
user phone is requested
found
data has been set
undefined
no phone number is here

Expo React native asks for location permissions in simulator but not in built app

App has been working fine for a while but now it's not prompting users for location permission which results in the features using location failing. On both simulator and physical device running expo go, the user is prompted to give permission to location data. (both iOS and Android) All other permissions it needs it asks for (push, camera and calendar)
export default function CheckIn(props) {
const { todayEvents } = useSelector(({ checkin }) => checkin);
const [venueIdd, setVenueId] = useState(0);
const [modalVisible, setModalVisible] = useState(false);
const [status, setStatus] = useState(null);
const [backgroundLocationPermission, setBackgroundLocationPermission] =
useState(null);
const dispatch = useDispatch();
TaskManager.defineTask("updateLocation", ({ data: { locations }, error }) => {
if (error) {
return;
}
dispatch(sendBackgroundLocation(locations[0].coords, venueIdd));
});
async function registerBackgroundFetchAsync() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 15, // 15 minutes
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
async function unregisterBackgroundFetchAsync() {
return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK);
}
React.useEffect(() => {
dispatch(getTodaysEvents());
const askPermission = async () => {
let getForeground = await Location.getForegroundPermissionsAsync();
if (getForeground.status !== "granted") {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
Alert.alert("Permission to access location was denied");
return;
} else {
let backgroundPermissions =
await Location.requestBackgroundPermissionsAsync();
if (backgroundPermissions.status == "granted") {
await AsyncStorage.setItem("background_permission", "true");
}
}
}
};
askPermission();
}, []);
const checkIn = async (index) => {
let temp = [...todayEvents];
temp[index].isCheckedIn = true;
setVenueId(temp[index].venueId);
const backgroundStatus = await AsyncStorage.getItem(
"background_permission"
);
// if (backgroundStatus !== null) {
Location.startLocationUpdatesAsync("updateLocation", {
timeInterval: 120,
distanceInterval: 0.01,
foregroundService: {
notificationTitle: "Company title",
notificationBody:
"Information in body",
},
accuracy: Location.Accuracy.Highest,
}).then((response) => {
// console.log("RESPONSE LOCATION", response);
});
// }
setTimeout(() => {
const stopped = Location.stopLocationUpdatesAsync("updateLocation");
}, 43200000);
let { coords } = await Location.getCurrentPositionAsync();
dispatch(userCheckIn({ lat: coords.latitude, long: coords.longitude }));
};
const checkOut = async (index) => {
const stopped = Location.stopLocationUpdatesAsync("updateLocation");
let temp = todayEvents;
temp[index].isCheckedIn = false;
//dispatch(userCheckOut()); // This is what I commented out
// And the two rows below is added
let { coords } = await Location.getCurrentPositionAsync();
dispatch(userCheckOut({ lat: coords.latitude, long: coords.longitude }));
};
const review = (index, value) => {
setModalVisible(true);
setTimeout(() => {
setModalVisible(false);
setTimeout(() => {
props.navigation.goBack();
}, 300);
}, 1500);
let temp = todayEvents;
temp[index].review = value;
dispatch(giveEventFeedBack(temp[index]));
};
From the app.json following iOS Location permissions are defined:
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
And for Android:
ACCESS_BACKGROUND_LOCATION
ACCESS_COARSE_LOCATION
ACCESS_FINE_LOCATION
There's a scenario when the user recently choose the NEVER ASK AGAIN option for that permission. The next time the app attempt to ask it, the request resolves in denied status silently without launching the request permission popup modal.
If this is the case, the app can force the user to grant permission manually in app settings
You can handle this situation like this
const permission = await Location.getForegroundPermissionsAsync();
// Detect if you can request this permission again
if (!permission.canAskAgain || permission.status === "denied") {
/**
* Code to open device setting then the user can manually grant the app
* that permission
*/
Linking.openSettings();
} else {
if (permission.status === "granted") {
// Your actually code require this permission
}
}
While the solution above doesn't work, Review this ongoing issue in Expo SDK 44 - https://github.com/expo/expo/issues/15273

react native image uri and async storage

i'm trying to save a profile picture and i save it with asyncStorage. store part working perfectly but if i close app and reopen it doesn`t show image.
i logging image uri and there is uri but cant solve the problem.
here is my code
this is image pick part
const ImagePick = async () => {
const options = {
title: 'Seçim yapınız.',
cancelButtonTitle: 'İptal',
takePhotoButtonTitle: 'Bir fotoğraf çek',
chooseFromLibraryButtonTitle: 'Galeriden seç',
chooseWhichLibraryTitle: 'Galeriden seç',
mediaType: 'photo',
storageOptions: {skipBackup: true, path: 'images'},
};
let isCameraPermitted = await requestCameraPermission();
let isStoragePermitted = await requestExternalWritePermission();
if (isCameraPermitted && isStoragePermitted) {
ImagePicker.showImagePicker(options, async response => {
//console.log('response', response);
if (response.didCancel) {
console.log('Kullanıcı fotoğraf seçimini iptal etti');
} else if (response.customButton) {
console.log('Özel butona tıklandı.');
} else if (response.error) {
console.log('error', 'Bir hata oluştu.');
} else {
console.log(response.fileName);
let uri = response.uri;
let path = response.uri;
if (Platform.OS === 'ios') {
path = '~' + path.substring(path.indexOf('/Documents'));
}
if (!response.fileName) {
response.fileName = path.split('/').pop();
}
let name = response.fileName;
let type = `image/${name.split('.').slice(-1)[0]}`;
console.log('uri', uri);
console.log('name', name);
console.log('type', type);
setImageUri(response.uri);
try {
await AsyncStorage.setItem('profilePicture', response.uri);
console.log('async storage kayıt başarılı');
} catch (error) {
console.log(error);
}
}
});
}
};
i get image like this
useEffect(() => {
getProfilePicture();
}, []);
const getProfilePicture = async () => {
const profilePicture = await AsyncStorage.getItem('profilePicture');
console.log('profilePicture', profilePicture);
if (profilePicture !== null) {
setImageUri(profilePicture);
setIsLoading(false);
} else {
setImageUri(
'https://t3.ftcdn.net/jpg/03/46/83/96/360_F_346839683_6nAPzbhpSkIpb8pmAwufkC7c5eD7wYws.jpg',
);
setIsLoading(false);
}
};
emulator is the problem. in device it's working

React Native UseEffect function is not working according to order

I want to get user's current location and set it into AsyncStorage a array. I will do it in the useEffect hook. But the problem is my functions are not working that according to given order. Here are my code
useEffect(() => {
getUserLocation();
setUserLocation();
check();
}, []);
/*Get User's Currunt Location*/
const getUserLocation = () => {
GetLocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 15000,
})
.then((location) => {
var lt = location.latitude;
var lg = location.longitude;
setlatitude(lt);
setlongitude(lg);
console.log("getUserLocation", lt, lg);
})
.catch((error) => {
const { code, message } = error;
console.warn(code, message);
});
};
/*Set User's Currunt Location to AsyncStorage*/
const setUserLocation = async () => {
try {
await AsyncStorage.setItem("user_location", JSON.stringify(userLocation));
console.log("setUserLocation", userLocation);
} catch (error) {
console.log("error setting user location");
}
};
const check = () => {
AsyncStorage.getItem("user_location", (err, result) => {
if (result !== null) {
console.log("check", result);
setlatitude(result.latitude);
setlongitude(result.longitude);
} else {
console.log("Data Not Found");
}
});
};
Whenever you use .then you are scheduling your code to run at some point in the future, when the promise has completed. So setUserLocation runs before the then of getUserLocation.
Also, it looks like your getUserLocation set react state, which won't be available until the next render. We use effects to manage this.
// Get the location on mount
useEffect(getUserLocation, []);
// Whenever the location updates, set it into storage
useEffect(() => setUserLocation().then(check), [latitude, longitude]);

How to get Absolute path of a file in react-native?

I am looking for a file picker in react-native which returns me Absolute Path of the file picked. I am currently using react-native-document-picker, but it gives me the relative path in the format of content://com.android.providers.media.documents/document/....... As I want to compress my video file, libraries like react-native-ffmpeg and react-native-video-processing require Absolute path of a file.
I actually figured this out myself. You can get Absolute path in 3 ways.
The most convenient way : Use react-native-document-picker, on selection it will give you a Relative path, something like this content://com.android....... Pass that Relative path to Stat(filepath) function of the react-native-fetch-blob library. The object will return absolute path. Append the path with file:// to use it for further operations.
The other 2 ways are by using react-native-image picker and CameraRoll (React Native Library)
I hope this helps !
Edit:
Please make sure you run the app on hardware device rather than Virtual Device to test it.
Install react-native-fetch-blob to get the path of the file.
Below is an example.
pickFile = async () => {
try {
const res = await DocumentPicker.pick({
type: [DocumentPicker.types.allFiles],
});
console.log(res.uri);
//output: content://com.android.providers.media.documents/document/image%3A4055
RNFetchBlob.fs
.stat(res.uri)
.then((stats) => {
console.log(stats.path);
//output: /storage/emulated/0/WhatsApp/Media/WhatsApp Images/IMG-20200831-WA0019.jpg
})
.catch((err) => {
console.log(err);
});
} catch (err) {
if (DocumentPicker.isCancel(err)) {
} else {
throw err;
}
}};
First you have to ask for Android Permissions so make sure you call this function first:
export const requestReadExternalStorage = () => {
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE);
};
After you call this function and Permissions are accepted you can pass the uri to this function:
export const getPath = (uri: string) => {
if (uri.startsWith('content://')) {
return RNFetchBlob.fs.stat(uri).then(info => info?.path);
}
return uri;
};
Then you just need to call it and use the real uri now, like this:
// res?.uri is the uri returned from the DocumentPicker.pick() response.
const uri = await getPath(res?.uri);
You may forget to request proper permissions for that like so (andriod only):
export async function requestStoragePermission() {
if (Platform.OS !== "android") return true
const pm1 = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE);
const pm2 = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);
if (pm1 && pm2) return true
const userResponse = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
]);
if (userResponse['android.permission.READ_EXTERNAL_STORAGE'] === 'granted' &&
userResponse['android.permission.WRITE_EXTERNAL_STORAGE'] === 'granted') {
return true
} else {
return false
}
}
Try this, maybe it will help you https://www.npmjs.com/package/react-native-file-share-for-android
But its support only for Android
const uploadDocunment = async finalSubmit => {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: 'Storage Permission',
message: 'App needs access to memory to download the file ',
},
);
if (granted != PermissionsAndroid.RESULTS.GRANTED) {
ToastAndroid.showWithGravity(
'You need to give storage permission to download the file',
ToastAndroid.SHORT,
ToastAndroid.BOTTOM,
);
return false;
}
try {
DocumentPicker.pick({
type: [DocumentPicker.types.plainText],
}).then(res => {
RNFetchBlob.fs.readFile(res.uri, 'utf8').then(text1 => {
ToastAndroid.showWithGravity(
'Docunment is Ready!',
ToastAndroid.SHORT,
ToastAndroid.BOTTOM,
);
});
});
} catch (err) {
if (DocumentPicker.isCancel(err)) {
ToastAndroid.showWithGravity(
'File not Selected',
ToastAndroid.SHORT,
ToastAndroid.BOTTOM,
);
} else {
throw err;
}
}
};
uploadDocunment();
};