react-native-fs: Unable to download file in Android 11 (ENOENT: no such file or directory, open '/storage/emulated/0/fileName.pdf')
I am trying to download the File in Android 11 but unable to download the file. Every time it’s giving ENOENT: no such file or directory, open ‘/storage/emulated/0/fileName.pdf’ error.
FileHelper.js
const { PermissionsAndroid, Platform } = require("react-native");
import RNFS from "react-native-fs";
const requestStoragePermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: "Storage Permission",
message: "requires Storage Permission",
buttonNegative: "Cancel",
buttonPositive: "OK",
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
return true;
} else {
return false;
}
} catch (err) {
console.warn(err);
return false;
}
};
export const FILE_TYPE = {
UPLOADING_START: 'uploadingStart',
UPLOADING: 'uploading',
DOWNLOADING_START: 'downloading_start',
DOWNLOADING: 'downloading',
FINISHED: 'finished',
};
export const downloadPDFFile = async (
callback,
serverFilePath = "https://www.mathworksheets4kids.com/add-sub/3by2-1.pdf",
serverFileName = "3by2-1.pdf",
) => {
try {
if (Platform.OS == 'android') {
const isPermissionGranted = await requestStoragePermission();
if (!isPermissionGranted) {
alert('You have cancelled the permission');
return;
}
}
let path;
let serveFileNameReplace = serverFileName.replace(/ /g, "%20");
let serverURL = serverFilePath.replace(/ /g, "%20");
if (Platform.OS == 'ios') {
path = `${RNFS.DocumentDirectoryPath}/${serveFileNameReplace}`;
} else {
path = `${RNFS.ExternalStorageDirectoryPath}/${serveFileNameReplace}`;
}
console.log("===>",path);
RNFS.downloadFile({
fromUrl: serverURL,
toFile: path,
background: false,
cacheable: false,
connectionTimeout: 60 * 1000,
readTimeout: 120 * 1000,
begin: (res) => {
console.log('begin :- ', res);
callback({
status: FILE_TYPE.DOWNLOADING_START,
percentage: 0,
});
},
progress: (res) => {
console.log('res :- ', res);
let percentage
if(res.contentLength == -1){
percentage = 0;
}else {
percentage = (res.bytesWritten * 100) / res.contentLength;
percentage = Math.round(percentage);
}
if (percentage >= 100) {
callback({
status: FILE_TYPE.FINISHED,
percentage: percentage,
});
} else {
callback({
status: FILE_TYPE.DOWNLOADING,
percentage: percentage,
path: path,
});
}
},
}).promise.then((res) => {
console.log('res :- ', res);
callback({
status: FILE_TYPE.FINISHED,
percentage: 100,
path: path,
});
}).catch((err) => {
console.log(err);
alert(err);
});
} catch (err) {
if (err.description === 'cancelled') {
// cancelled by user
}
console.log(err);
}
};```
App.js
import React from 'react';
import {
SafeAreaView,
View,
Text,
StatusBar,
TouchableOpacity,
} from 'react-native';
import {downloadPDFFile} from './src/FileHelper';
const App = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<View style={{flex:1, marginTop: '50%', marginLeft: '10%'}}>
<TouchableOpacity
style={{
justifyContent:'center',
alignItems:'center',
width: '80%',
height: 40,
backgroundColor: 'green'
}}
onPress={() => {
downloadPDFFile((res) => {
console.log(res);
});
}}>
<Text style={{color: '#000000'}}>Download File</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</>);
};
export default App;
AndroidManifest:-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.awesomeprojectandroid11">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:preserveLegacyExternalStorage="true"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
react-native-fs I am using to download the File. But it's not working.
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 11
- Comments: 39
setting this solves the file download error on Android 10/11
If you want to be able to download a document to android storage, you must request user permission for
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
before downloading and saving to storage, as mentioned by @Yandamuri . But if you also want to view/open the document for the user, you must also request user permissionPermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE
before attempting to open it.For older versions of android, you must add the necessary permissions from above (see below) to AndroidManifest.xml under
manifest
tag beforeapplication
tag. These are requested when installing the app rather than when using them and this applies to other android permissions as well (bluetooth, camera etc).try for the permission in following way,
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);
Add this permission to manifest file :
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
Use below code to save file No need allow any storage permission in > Q for the same:
if (SDK_INT >= Build.VERSION_CODES.Q) { ContentResolver resolver = context.getContentResolver(); ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4"); contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, dir); Uri videoUri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues); OutputStream output = resolver.openOutputStream(videoUri); output.write(data, 0, count); output.close(); }
Hello, since by the 5th of May Google Play will ignore this flag and remove app that do not use API like Media Store or Storage Access Framework how should we handle this?
For android 11 + Try this let dirPath = ${RNFS.ExternalStorageDirectoryPath}/Documents/APP name Don’t try to create App directory in root… use any existing folder present already in root and create app folder inside that folder… In my case, I have created inside Documents
found any solution to save files in android 11?
Also, keep in mind that android 11 added scoped storage for apps by default and all react-native packages that I know of are not yet prepared to handle this. Until a next android version, you can opt out of this as mentioned here: https://developer.android.com/about/versions/11/privacy/storage.
So you probably also need to set
requestLegacyExternalStorage="true"
on theapplication
node in the manifest for using the old storage model also on android 11, check android developer documentation for more info. In the link above it is stated that: The default value is: - false for apps with targetSdkVersion >= 29 (Q). - true for apps with targetSdkVersion < 29.@Shreyakkk Your solution seems to be working for me on Android 12. Thanks.
EDIT: Throughout the day I found that if I delete a downloaded file from my device’s file system,
RNFS.ExternalStorageDirectoryPath
orRNFS. DownloadDirectoryPath
would throw this error IF the app tries to download another file by the same name again. So for now I’ve addedDate.now()
totoFile
to make sure every file the app downloads has a different name (thus a different path).Perhaps my experience so far is not extensive enough to make any definitive conclusions, but this sure seems weird.
Hi @trungitvn I am getting the same error for Android 10. I guess your solution might work for me, but I don’t understand your code completely. Is
DownloadManager
another library that you are using? Really appreciate it if you could give some help. Thanks@parameshwari5 Of course, it will work. But it won’t work with version 30 and Google Play require the
maxSdkVersion
to be 30 now.you can use: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) { mPath= getActivity().getExternalFilesDir(Environment.DIRECTORY_DCIM) + “/” + now + “.jpeg”; } else { mPath= Environment.getExternalStorageDirectory().toString() + “/” + now + “.jpeg”; }
I noticed the same error message and it had nothing to do with permissions.
The problem was that I was trying to create a file with a name that was already taken on the Downloads directory.
I found the solution for myself. Hope to help someone!
@gabrielporcher I am also using target SDK 29 with android:requestLegacyExternalStorage=“true” and can confirm that you can successfully upload the app to Play Store. Yet they have notified in Play Console that after august the app will be required to target SDK 30 and android:requestLegacyExternalStorage=“true” will not work anymore, forcing us to use the new scoped storage.
I am also confused as I don’t fully understand the Scoped Storage, nor think that any react native packages are ready for this change at this moment.
Hello @Yandamuri, this is the reference: docs.
The flag
will be ignored soon and also app uploaded with this flag enable are receiving a warning form Google about this fact.
Thank you!
@marf is there any official note regarding this? if so, Can you please ping me the link?