Skip to content
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

Unable to download file in Android 11 (ENOENT: no such file or directory, open '/storage/emulated/0/fileName.pdf') #950

Open
pankajnegi1893 opened this issue Nov 13, 2020 · 40 comments

Comments

@pankajnegi1893
Copy link

pankajnegi1893 commented Nov 13, 2020

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. 
@moygospadin
Copy link

Hello do u solve the problem?

@xgenem
Copy link

xgenem commented Feb 3, 2021

Seriously...

@Yandamuri
Copy link

try for the permission in following way,

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

@R4DIC4L
Copy link

R4DIC4L commented Feb 9, 2021

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>

@Yandamuri
Copy link

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

@R4DIC4L
Copy link

R4DIC4L commented Feb 13, 2021

@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
Copy link

R4DIC4L commented Feb 13, 2021

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.

@i1990jain
Copy link

<application
        android:requestLegacyExternalStorage="true"

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

@marf
Copy link

marf commented Apr 15, 2021

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?

@Yandamuri
Copy link

Yandamuri commented Apr 20, 2021

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

@marf
Copy link

marf commented Apr 20, 2021

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!

@cstehreem
Copy link

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

@rcerrejon
Copy link

@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

@marf
Copy link

marf commented Apr 27, 2021

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

@R4DIC4L
Copy link

R4DIC4L commented Apr 29, 2021

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
Copy link

R4DIC4L commented Apr 29, 2021

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.

@guofoo
Copy link

guofoo commented May 7, 2021

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

@gabrielporcher
Copy link

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

@R4DIC4L
Copy link

R4DIC4L commented May 20, 2021

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

@enigmablue
Copy link

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

#395 (comment)

@santiconte
Copy link

found any solution to save files in android 11?

@ashish-hiverhq
Copy link

August is almost over, any updates on the issue?

@AdnanAshraf7
Copy link

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

@malgorzatatanska
Copy link

@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

@tusharaddeveloper
Copy link

tusharaddeveloper commented Sep 4, 2021

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

@smismailalam
Copy link

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

@trungitvn
Copy link

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

@enigmablue
Copy link

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

@trungitvn
Copy link

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

@nriccar
Copy link

nriccar commented Nov 22, 2021

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.

@parameshwari5
Copy link

parameshwari5 commented Nov 25, 2021

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";
}

@niyasc
Copy link

niyasc commented Dec 6, 2021

@parameshwari5 Of course, it will work. But it won't work with version 30 and Google Play require the maxSdkVersion to be 30 now.

@dilantha111
Copy link

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

@Shreyakkk
Copy link

Shreyakkk commented Jan 14, 2022

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

@UVStudio
Copy link

UVStudio commented Jan 27, 2022

@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 or RNFS. DownloadDirectoryPath would throw this error IF the app tries to download another file by the same name again. So for now I've added Date.now() to toFile 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.

@jgudo
Copy link

jgudo commented Feb 22, 2022

@Shreyakkk Your solution works for me!

@PhilippLeh21
Copy link

@Shreyakkk @UVStudio @jgudo I got the same error: If I download a file for the first time it works perfectly fine. When I delete the file and download it again I get the "no such file or directory" error. I am not able to reproduce this error on every device. Do you have any solution for this besides putting Date.now() into the file name?

@edwardkes
Copy link

@PhilippLeh21 @UVStudio Did you guys do something other than adding Date().now() to the file name?

@chizhkov422
Copy link

Hello, i have a temporary solution. You can download the file to TemporaryDirectoryPath than copying the file to the downloads folder. Here is my example.

import FS from "react-native-fs";
import { FileSystem } from "react-native-file-access";

const res = await FS.downloadFile({
fromUrl: link,
toFile: `${FS.TemporaryDirectoryPath}/${fileName}`,
}).promise;

if (res.statusCode !== 200) return;

FileSystem.cpExternal(`${FS.TemporaryDirectoryPath}/${fileName}`, fileName, "downloads");

@Gambitboy
Copy link

Can confirm @chizhkov422 's solution worked. I didn't copy my data but I switched on to the temp directory on the later versions of android and its working now. Wonder if its a permission issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests