react-native-fs icon indicating copy to clipboard operation
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')

Open pankajnegi1893 opened this issue 4 years ago • 40 comments

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. 

pankajnegi1893 avatar Nov 13 '20 15:11 pankajnegi1893

Hello do u solve the problem?

moygospadin avatar Jan 08 '21 04:01 moygospadin

Seriously...

xgenem avatar Feb 03 '21 20:02 xgenem

try for the permission in following way,

const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);

Yandamuri avatar Feb 09 '21 09:02 Yandamuri

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 avatar Feb 09 '21 10:02 R4DIC4L

@R4DIC4L Can you please let us know up to what version of android permissions need to be mentioned in Androidmanifest.xml?

Yandamuri avatar Feb 13 '21 04:02 Yandamuri

@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.

R4DIC4L avatar Feb 13 '21 05:02 R4DIC4L

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.

R4DIC4L avatar Feb 13 '21 06:02 R4DIC4L

<application
        android:requestLegacyExternalStorage="true"

setting this solves the file download error on Android 10/11

i1990jain avatar Feb 19 '21 15:02 i1990jain

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 avatar Apr 15 '21 18:04 marf

@marf is there any official note regarding this? if so, Can you please ping me the link?

Yandamuri avatar Apr 20 '21 05:04 Yandamuri

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!

marf avatar Apr 20 '21 07:04 marf

Has anyone found a workaround for this? Since the May 5th deadline is pretty close..

cstehreem avatar Apr 25 '21 15:04 cstehreem

@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 avatar Apr 27 '21 07:04 rcerrejon

@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.

marf avatar Apr 27 '21 09:04 marf

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.

R4DIC4L avatar Apr 29 '21 05:04 R4DIC4L

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 avatar Apr 29 '21 05:04 R4DIC4L

Trying to tie together all the related issues to the root issue: #998

guofoo avatar May 07 '21 09:05 guofoo

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 avatar May 20 '21 14:05 gabrielporcher

@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.

R4DIC4L avatar May 20 '21 20:05 R4DIC4L

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

enigmablue avatar Jun 03 '21 02:06 enigmablue

found any solution to save files in android 11?

santiconte avatar Jul 20 '21 20:07 santiconte

August is almost over, any updates on the issue?

ashish-hiverhq avatar Aug 30 '21 12:08 ashish-hiverhq

android:requestLegacyExternalStorage="true" Adding this to android manifest worked for me on android 8,9,10,11

AdnanAshraf7 avatar Sep 01 '21 05:09 AdnanAshraf7

@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

malgorzatatanska avatar Sep 01 '21 06:09 malgorzatatanska

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(); }

tusharaddeveloper avatar Sep 04 '21 09:09 tusharaddeveloper

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

smismailalam avatar Oct 13 '21 15:10 smismailalam

Hellow everybody! Is there any solution yet? - Hope someone can help me with this, please!

trungitvn avatar Nov 14 '21 08:11 trungitvn

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

enigmablue avatar Nov 14 '21 08:11 enigmablue

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();
    }

trungitvn avatar Nov 15 '21 06:11 trungitvn

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.

nriccar avatar Nov 22 '21 15:11 nriccar