2

My React Native Android application needs to save a JSON file in the special folder located in the external storage. I am trying to do it using RNFS (https://github.com/itinance/react-native-fs) this way:

const saveData = async () => {
    var path = `${RNFS.ExternalStorageDirectoryPath}/MyApp`;
    RNFS.mkdir(path);
    path += '/data.json';
    RNFS.writeFile(path, JSON.stringify(getData()), 'utf8')
      .then((success) => {
        console.log('Success');
      })
      .catch((err) => {
        console.log(err.message);
      });
  }

It works well but fails on Android Q device. This error is shown:

Error: Directory could not be created

If I try to write a plain file without creating directory it throws this error:

ENOENT: open failed: ENOENT (No such file or directory), open '/storage/emulated/0/data.json'

However, I've added this permissions to my AndroidManifest.xml:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

And granted the external storage permissions in the settings. But if I change the RNFS.ExternalStorageDirectoryPath to RNFS.DocumentDirectoryPath it works without any errors. But I need to get an access to the external storage. Is there any way to do it?

anDev
  • 379
  • 6
  • 16
  • Yes on Android Q getExternalStorageDirectory() is not accessable anymore. You can read that here every day. Also how to use legacyExternalStorage in manifest to keep it working for Q. But better use a different directory to be ready for R. – blackapps Aug 13 '20 at 09:59
  • `RNFS.DocumentDirectoryPath` Which path would that be? – blackapps Aug 13 '20 at 10:00
  • @blackapps, it's `data/user/0/com.appName/files/` – anDev Aug 13 '20 at 10:05
  • /data/user/0/com.appName/files/ would be from getFilesDir(). The apps private directory. You do not need any permission then. – blackapps Aug 13 '20 at 10:07
  • `But I need to get an access to the external storage.` Please explain why. – blackapps Aug 13 '20 at 10:10
  • @blackapps, user has an abbility to create backups and restore them. Backup file must be available for user to edit, move or share it – anDev Aug 13 '20 at 10:14
  • Ok. You did not tell that the user would use a file manager or other app to do such. Or does your app provide that possibility? – blackapps Aug 13 '20 at 10:15
  • @blackapps, yes, I tell user the location of the saved file, but it doesn't accessible becaues data folder requires a superuser rights. I want to make a special folder in the external storage, like in WhatsApp – anDev Aug 13 '20 at 10:48
  • You did not tell me if the user uses a file manager or an app others than yours to do so. – blackapps Aug 13 '20 at 10:49

2 Answers2

3

I've found out that legacy external storage access is required from Android API 29+. So, I've editied my AndroidManifest.xml (that located in android/app/src/main/) like this:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.appName">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
    ...
      android:requestLegacyExternalStorage="true"
    ...
    >
    </application>
</manifest>

And everything has started to work. Also, I've added a request for granting permissions to the saveData function:

const saveData = async () => {
    try {
      const granted = await PermissionsAndroid.requestMultiple([
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
      ]);
    } catch (err) {
      console.warn(err);
    }
    const readGranted = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE); 
    const writeGranted = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);
    if(!readGranted || !writeGranted) {
      console.log('Read and write permissions have not been granted');
      return;
    }
    var path = `${RNFS.ExternalStorageDirectoryPath}/MyApp`;
    RNFS.mkdir(path);
    path += '/data.json';
    RNFS.writeFile(path, JSON.stringify(getData()), 'utf8')
      .then((success) => {
        console.log('Success');
      })
      .catch((err) => {
        console.log(err.message);
      });
  }
anDev
  • 379
  • 6
  • 16
  • Ok. But this works for 29. Not for 30 as you suggested with 29+. This only gives you some time extra to solve your problem. – blackapps Aug 13 '20 at 12:40
  • @blackapps, What should I do to make it work on API 30? – anDev Aug 13 '20 at 18:10
  • There will be another RNSF.ExternalFilesPath or something like that. Equivalent with getExternalFilesDir(). No permissions needed. – blackapps Aug 13 '20 at 19:25
1

With Android version 10 (API level 29) the concept of scoped storage is introduced. According to Docs-

"To give users more control over their files and to limit file clutter, apps that target Android 10 (API level 29) and higher are given scoped access into external storage, or scoped storage, by default. Such apps have access only to the app-specific directory on external storage, as well as specific types of media that the app has created."

For Android version 10, you have the option to opt-out of scoped storage by adding android:requestLegacyExternalStorage="true" in the AndroidManifest.xml like this->

<application ...
 android:requestLegacyExternalStorage="true"
...>
....
</application>

This is a temporary solution and will work only for android version 10 and not above it. Also make sure you ask run-time permission for read/write external storage.

mohit arora
  • 131
  • 2
  • 4