New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expo-media-library createAssetAsync calls error in Android 11 even with MediaLibrary Permission. #12060
Comments
Hi, I have exactly the same issue ! 👎 Thanks in advance for help! The error is located in CreateAsset.java line 97. |
Same here |
Any solutions ? I have the same issue, When I call createAssetAsync I have the following error : Unable to copy file into external storage. |
Same here... Expo-media-library createAssetAsync calls error "Unable to copy file into external storage." in Android 11. The app that I'm currently working on downloads all kinds of files (pdf, jpg,png,excell,word...) and before the upgrade to expo 41 it was working as expected. Any solution or hint will be much appreciated. Thanks |
Hi- can someone share a Snack link that we can use to reproduce this error? |
Hi, here is the Snack to reproduce the error on Android 11. https://snack.expo.io/@remato/anxious-churros Any other ideas how to download .xlsx files to user-accessible location would be appreciated, because this is blocking us from using SDK41. One way to do this on Android 11 without using media-library is using StorageAccessFramework, but this includes so many steps and is too complicated for users:
|
To save time of people having the same problem as me, this is my code. import AsyncStorage from "@react-native-async-storage/async-storage";
import * as FileSystem from "expo-file-system";
import * as MediaLibrary from "expo-media-library";
static settings = async (newSettings) => {
return new Promise(async (resolve, reject) => {
try {
let settings = (await AsyncStorage.getItem("settings").then((result) => JSON.parse(result))) || {};
if (newSettings) {
settings = Object.assign(settings, newSettings);
await AsyncStorage.setItem("settings", JSON.stringify(settings));
}
return resolve(settings);
} catch (e) {
console.log("Error in settings", e);
return resolve({});
}
});
};
static getDirectoryPermissions = async (onDirectoryChange) => {
return new Promise(async (resolve, reject) => {
try {
const initial = FileSystem.StorageAccessFramework.getUriForDirectoryInRoot();
onDirectoryChange({isSelecting: true}) //For handle appStateChange and loading
const permissions = await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync(initial);
this.settings({ downloadsFolder: permissions.granted ? permissions.directoryUri : null });
// Unfortunately, StorageAccessFramework has no way to read a previously specified folder without popping up a selector.
// Save the address to avoid asking for the download folder every time
onDirectoryChange({downloadsFolder: permissions.granted ? permissions.directoryUri : null, isSelecting: false})
return resolve(permissions.granted ? permissions.directoryUri : null);
} catch (e) {
console.log("Error in getDirectoryPermissions", e);
onDirectoryChange({downloadsFolder: null})
return resolve(null);
}
});
};
static downloadFilesAsync = async (files, onDirectoryChange) => {
// files = [{url: "url", fileName: "new file name" + "extension", mimeType: is_video ? "video/mp4" : "image/jpg"}]
// onDirectoryChange = () => {cb_something_like_setState()}
return new Promise(async (resolve, reject) => {
try {
const mediaLibraryPermission = await this.getMediaLibraryPermission()
if (mediaLibraryPermission !== "granted") {
return resolve({status: "error"})
}
let settings = await this.settings();
// Unfortunately, StorageAccessFramework has no way to read a previously specified folder without popping up a selector.
// Save the address to avoid asking for the download folder every time
const androidSDK = Platform.constants.Version
if (Platform.OS === "android" && androidSDK >= 30 && !settings.downloadsFolder) {
//Except for Android 11, using the media library works stably
settings.downloadsFolder = await this.getDirectoryPermissions(onDirectoryChange)
}
const results = await Promise.all(
files.map(async (file) => {
try {
if (file.url.includes("https://")) {
// Remote file
const { uri, status, headers, md5 } = await FileSystem.downloadAsync(
file.url,
FileSystem.cacheDirectory + file.name
);
file.url = uri; //local file(file:///data/~~~/content.jpg)
// The document says to exclude the extension, but without the extension, the saved file cannot be viewed in the Gallery app.
}
if (Platform.OS === "android" && settings.downloadsFolder) {
// Creating files using SAF
// I think this code should be in the documentation as an example
const fileString = await FileSystem.readAsStringAsync(file.url, { encoding: FileSystem.EncodingType.Base64 });
const newFile = await FileSystem.StorageAccessFramework.createFileAsync(
settings.downloadsFolder,
file.name,
file.mimeType
);
await FileSystem.writeAsStringAsync(newFile, fileString, { encoding: FileSystem.EncodingType.Base64 });
} else {
// Creating files using MediaLibrary
const asset = await MediaLibrary.createAssetAsync(file.url);
}
return Promise.resolve({status: "ok"});
} catch (e) {
console.log(e)
return Promise.resolve({status: "error"});
}
})
);
return resolve({ status: results.every((result) => result.status === "ok") ? "ok" : "error" });
} catch (e) {
console.log("Error in downloadFilesAsync", e)
return resolve({ status: "error" });
}
});
}; |
Is there any progress with this? I've updated to expo 42 and the issue is still here. |
Hi, I'm having the same issue, I'm leaving the try-catch error stack in case it helps the devs to understand the problem. Notice I change my local ip for {my-ip} in the json.
|
I tried your code that I'm able to save the file but 2 issues: -
|
Hello @barthap , Any guidance would be appreciated Facing the same issue on Expo 45.0.0.
Reproduce devices: Oneplus Nord (Android Version 11) & mi Xiaomi 11 lite NE (Android Version 12) |
This is because the Media library is able to save image/video/audio assets so it will fail with other file types. if (Platform.OS === 'android') {
const downloadDir = SAF.getUriForDirectoryInRoot('Download');
const permission = await SAF.requestDirectoryPermissionsAsync(downloadDir);
if (!permission.granted) {
return false;
}
const destinationUri = await SAF.createFileAsync(permission.directoryUri, filename, mimeType);
// TODO: Error in SAF file: Destination '[...] /Download/SelectedDir' directory cannot be created
// await SAF.copyAsync({ from: uri, to: destinationUri });
await SAF.writeAsStringAsync(destinationUri, await readAsStringAsync(uri));
return true;
} else if (Platform.OS === 'ios') {
await Sharing.shareAsync(uri, {
mimeType,
UTI: uti,
});
return true;
} However, there is one disadvantage:
|
Hello @barthap thanks for the quick response, I'm trying to save the .xlsx file, its save in the Download/Data folder with a proper name when opening that file gets corrupted. Reproducable example : snack expo const downloadExcel = async () => {
setError(null);
const fileUrl =
'https://filesamples.com/samples/document/xlsx/sample1.xlsx';
const fileName = 'sample1.xlsx';
FileSystem.downloadAsync(fileUrl, FileSystem.documentDirectory + fileName)
.then(({ uri }) => {
console.log('Finished downloading to ', uri);
// saveFile(uri);
// saveF(uri);
saveData(uri);
})
.catch((error) => {
setError(error);
});
};
const saveData = async (uri: string) => {
try {
setLoading(true);
console.log({ uri });
if (uri) {
const mimeType =
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const filename = 'test.xlsx';
if (Platform.OS === 'android') {
const downloadDir =
FileSystem.StorageAccessFramework.getUriForDirectoryInRoot(
'Download/Data'
);
const permission =
await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync(
downloadDir
);
console.log({ permission });
if (!permission.granted) {
return false;
}
const destinationUri =
await FileSystem.StorageAccessFramework.createFileAsync(
permission.directoryUri,
filename,
mimeType
);
console.log({ destinationUri });
// TODO: Error in SAF file: Destination '[...] /Download/SelectedDir' directory cannot be created
// await StorageAccessFramework.copyAsync({ from: uri, to: destinationUri });
const contents =
await FileSystem.StorageAccessFramework.readAsStringAsync(uri);
console.log({ contents });
const resp =
await FileSystem.StorageAccessFramework.writeAsStringAsync(
destinationUri,
contents
);
console.log({ resp });
return true;
} else if (Platform.OS === 'ios') {
// Need to add dependancy
// await Sharing.shareAsync(uri, {
// mimeType,
// // UTI: uti,
// });
return true;
}
}
} catch (error) {
console.log({ error });
} finally {
setLoading(false);
}
}; |
I have the same problem @durgesh2007 did you find any solution |
|
what should be passed in settings |
The settings function was created to store the values set by the user while using the app. |
I had the same problem, after cleaning the folder where the files were being saved, in my case the /DCIM folder, solved the problem |
for me it worked the same |
I used this solution: #12060 (comment) But I had to clear the contents of my DCIM folder once. |
in my case, for android device.
|
This is my code to save remote images to gallery which works both on IOS and Android in Expo 50, today, 03/03/2024 |
Thank you soo much, this worked for me! |
Summary
Expo-media-library createAssetAsync calls error "Unable to copy file into external storage." in Android 11 even with MediaLibrary Permission.
MediaLibrary does not require MANAGE_EXTERNAL_STORAGE permission.
But always show error message in Android 11. Works fine in others.
Both SDK 40 and SDK 41 give the same result.
Is there a way to replace it with StorageAccessFramework?
Isn't SAF only able to create string or empty files?
I tried to grant access to the DCIM folder using SAF, but createAssetAsync still doesn't work.
Managed or bare workflow? If you have
ios/
orandroid/
directories in your project, the answer is bare!managed
What platform(s) does this occur on?
Android
SDK Version (managed workflow only)
40.0.0, 41.0.0.
Environment
Expo CLI 4.1.6 environment info:
System:
OS: Windows 10 10.0.19041
Binaries:
Node: 14.15.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.10 - C:\Users\Administrator\AppData\Roaming\npm\yarn.CMD
npm: 6.14.9 - C:\Users\Administrator\AppData\Roaming\npm\npm.CMD
IDEs:
Android Studio: Version 4.1.0.0 AI-201.8743.12.41.6953283
npmPackages:
expo: ^40.0.0 => 40.0.1 (And 41.0.0)
react: 16.13.1 => 16.13.1
react-dom: 16.13.1 => 16.13.1
react-native: https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz => 0.63.2
react-native-web: ~0.13.12 => 0.13.18
Expo Workflow: managed
Device info:
Samsung Galaxy S21 Ultra 5G / Android 11 / Internal storage only
Reproducible demo or steps to reproduce from a blank project
The text was updated successfully, but these errors were encountered: