react-native-fs
react-native-fs copied to clipboard
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.
Hello do u solve the problem?
Seriously...
try for the permission in following way,
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);
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 permission PermissionsAndroid.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 before application
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).
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="<your package name here>">
...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
<application
....
</application>
</manifest>
@R4DIC4L Can you please let us know up to what version of android permissions need to be mentioned in Androidmanifest.xml?
@Yandamuri As mentioned in https://developer.android.com/guide/topics/manifest/uses-permission-element: Permissions are granted by the user when the application is installed (on devices running Android 5.1 and lower) or while the app is running (on devices running Android 6.0 and higher).
I find it good practice to also include the permisions in the manifest since for android 5.1 and below it is easy to do and handled by the Play Store automatically at install time (you receive Granted when asking in code by default as the user acknowledges all necessary permissions when agreeing to install your app), permissions also show in the app page when you tap See permissions in About page. But you can skip this if your users must have Android 6 and above to install the app.
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 the application
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.
<application
android:requestLegacyExternalStorage="true"
setting this solves the file download error on Android 10/11
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?
@marf is there any official note regarding this? if so, Can you please ping me the link?
Hello @Yandamuri, this is the reference: docs.
The flag
<application
android:requestLegacyExternalStorage="true"
will be ignored soon and also app uploaded with this flag enable are receiving a warning form Google about this fact.
Thank you!
Has anyone found a workaround for this? Since the May 5th deadline is pretty close..
@marf For an update from the app I found a backward compatibility with legacyExternalStorage, but is only for updates, not for new installations. with the flag:
android/app/src/main/AndroidManifest.xml
<application
android:preserveLegacyExternalStorage="true"
API 30 is needed to found the flag android:preserveLegacyExternalStorage.
android/build.gradle
targetSdkVersion = 30
compileSdkVersion = 30
@rcerrejon
The problem is that Google said that after the 5th of May this flag will be ignored since it purpose was only to help developers during this transition period which is basically ending now.
I see the following notice in Google Play:
Developers with apps on devices running Android 11+ must use Scoped Storage to give users better access control over their device storage. To release your app on Android 11 or newer after May 5th, you must either:
Update your app to use more privacy friendly best practices, such as the Storage Access Framework or Media Store API Update your app to declare the All files access (MANAGE_EXTERNAL_STORAGE) permission in the manifest file, and complete the All files access permission declaration in Play Console from May 5th Remove the All files access permission from your app entirely For apps targeting Android 11, the requestLegacyExternalStorage flag will be ignored. You must use the All files access permission to retain broad access.
Apps requesting access to the All files access permission without a permitted use will be removed from Google Play, and you won't be able to publish updates.
From what I understand, a temporary option until Storage Access is available in a react native package, we can also declare the MANAGE_EXTERNAL_STORAGE permission in the manifest (I already have READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE) and it will act as All files permission. This means the user will have to give explicit permission for the app managing their external storage as well. This probably has to be requested for newer APIs together with the other 2 when trying to access the storage.
More details on All files access can be found here: https://developer.android.com/training/data-storage/manage-all-files#all-files-access-google-play.
Has anyone tried to upload a new build to Play Store (even for internal/beta testing)? I am unsure whether this would actually work.
Trying to tie together all the related issues to the root issue: #998
More details on All files access can be found here: https://developer.android.com/training/data-storage/manage-all-files#all-files-access-google-play.
Has anyone tried to upload a new build to Play Store (even for internal/beta testing)? I am unsure whether this would actually work.
@R4DIC4L I did some internal testing last week, and beta testing today. My app is using targetSdkVersion 29, and I'm still using android:requestLegacyExternalStorage="true". The store question is fine, my app still working and appearing at the store.
But I'm confused about this new storage strategy.
At the moment, seems like react-native-fs doesnt work without android:requestLegacyExternalStorage="true".
@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.
Am unable to send a pdf despite having android:requestLegacyExternalStorage="true" as well as both WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE .
Gives EACCES (Permission denied), when trying to read it with RNFS (but stat works), and unable to upload it (socket closed).
https://github.com/itinance/react-native-fs/pull/395#issuecomment-853505267
found any solution to save files in android 11?
August is almost over, any updates on the issue?
android:requestLegacyExternalStorage="true" Adding this to android manifest worked for me on android 8,9,10,11
@AdnanAshraf7 android:requestLegacyExternalStorage="true" is working well if you are targeting Sdk29, but after August 2021 you will have to target Sdk30 where this requestLegacyExternalStorage flag will be ignored. #998
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, any updates on the issue? i am also facing same issue in android 11 also Adding this to android manifest file android:requestLegacyExternalStorage="true" but no success
Hellow everybody! Is there any solution yet? - Hope someone can help me with this, please!
i use react-native-android-uri-path. But i dont know if it will solve all of your issues. I mostly use it for content:// files but i think it still works.
import getPath from '@flyerhq/react-native-android-uri-path';
and getPath on your path to get all the absolute path
I found the solution for myself. Hope to help someone!
- In AndroidManifest.xml set:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" /> //For <= Android 10
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:requestLegacyExternalStorage="true" //For Android 10
...
- To download and save a file for Android 11, i use code:
public static long downloadFile(Context context, File file) {
String filePath = file.getAbsolutePath();
Uri downloadUri = Uri.parse(URL);
DownloadManager.Request request = new DownloadManager.Request(downloadUri);
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI
| DownloadManager.Request.NETWORK_MOBILE);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filePath);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
return downloadManager.enqueue(request);
}
- To check exists file:
static public File getAbsoluteFile(String relativePath) {
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
relativePath);
}
static public boolean isDownloaded(File file) {
File file = getAbsoluteFile(filePath);
return file.exists();
}
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.