# Problem with writeExternalStorage runtime permissions on Android

**URL:** <https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408>\
**Category:** Android\
**Created:** [October 18, 2022, 12:02pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408 "2022-10-18T12:02:14Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![ervoL](https://avatars.discourse-cdn.com/v4/letter/e/9de0a6/32.png) [@ervoL](https://forum.juce.com/u/ervoL)\
**Post date:** [October 18, 2022, 12:02pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/1 "2022-10-18T12:02:14Z")

</div>

Dear (pro)Jucers!

I’m having difficulties with runtime permissions check for _writeExternalStorage_. When I try to ask for this permission I run into _jassertfalse_ in _void RuntimePermissions::request (PermissionID permission, Callback callback)_ saying that I need to declare this permission also in manifest.

The problem is that it is in manifest already there:

```auto
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>

```

I’m using standard boilerplate code:

```auto
if (!RuntimePermissions::isGranted(RuntimePermissions::writeExternalStorage))
    {
        SafePointer<EQ> safeThis(this);
        RuntimePermissions::request(RuntimePermissions::writeExternalStorage,
            [safeThis](bool granted) mutable
            {
                if (safeThis != nullptr && granted)
                    safeThis->_saveEqValsToFile();
            });
        return;
    }

```

The same issue I have with “official” AudioRecordingDemo so it should be that my code or Projucer settings are ok.  
Any ideas?

Also it is maybe important to say that other runtime permissions like microphone and readExternalStorage work fine.  
I’m using Juce 7.0.2.

Thanks!

---

<div class="post-metadata">

**Author:** ![reuk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/reuk/32/21494_2.png) [@reuk](https://forum.juce.com/u/reuk)\
**Post date:** [October 18, 2022, 12:14pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/2 "2022-10-18T12:14:56Z")

</div>

Which version of Android are you targeting? Note that for apps targeting SDK 29+, this permission doesn’t do anything:

> **[Storage updates in Android 11  |  Android Developers](https://developer.android.com/about/versions/11/privacy/storage#permissions-target-11)**

Instead, you can show a native ‘save’ file dialog to allow the user to select a directory where your app may write. Then, you can use `juce::AndroidDocument` to write into the selected location.

---

<div class="post-metadata">

**Author:** ![ervoL](https://avatars.discourse-cdn.com/v4/letter/e/9de0a6/32.png) [@ervoL](https://forum.juce.com/u/ervoL)\
**Post date:** [October 18, 2022, 1:42pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/3 "2022-10-18T13:42:22Z")

</div>

Hi @reuk! Thank you on your swift response.  
I’m running my app on Android 11 (SDK version 30) so your problem description makes sense but how can I make it work if I want to cover Android versions that require this permission and those that don’t?

Should I check for SDK version and if it is lower than 29 ask for permission and if it is higher then don’t?

Do you maybe have an example that I can scavenge? 🤓

---

<div class="post-metadata">

**Author:** ![reuk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/reuk/32/21494_2.png) [@reuk](https://forum.juce.com/u/reuk)\
**Post date:** [October 18, 2022, 1:53pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/4 "2022-10-18T13:53:34Z")

</div>

I don’t think it should be necessary to check the API level.

The AudioPlaybackDemo shows how files can be read on different Android versions. Writing files should work in a similar way:

> <https://github.com/juce-framework/JUCE/blob/d3cff375be396580439ef287159b0665dbf2f259/examples/Audio/AudioPlaybackDemo.h#L493>

---

<div class="post-metadata">

**Author:** ![ervoL](https://avatars.discourse-cdn.com/v4/letter/e/9de0a6/32.png) [@ervoL](https://forum.juce.com/u/ervoL)\
**Post date:** [October 18, 2022, 3:08pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/5 "2022-10-18T15:08:52Z")

</div>

Thanks @reuk! Well if I have that boilerplate code for runtime permission check, I get that assertion I mentioned above so some sdk version check is needed I think 🤔 Do you know maybe how to do that?

With this code I managed to write and read from external memory but could you check if you maybe see some problems in it:

```auto
inline std::unique_ptr<OutputStream> makeOutputStream (const URL& url)
{
#if JUCE_ANDROID
    if (auto doc = AndroidDocument::fromDocument (url))
        return doc.createOutputStream();
#endif

#if ! JUCE_IOS
    if (url.isLocalFile())
        return url.getLocalFile().createOutputStream();
#endif

    return url.createOutputStream();
}

inline std::unique_ptr<InputSource> makeInputSource (const URL& url)
{
#if JUCE_ANDROID
    if (auto doc = AndroidDocument::fromDocument (url))
        return std::make_unique<AndroidDocumentInputSource> (doc);
#endif

#if ! JUCE_IOS
    if (url.isLocalFile())
        return std::make_unique<FileInputSource> (url.getLocalFile());
#endif

    return std::make_unique<URLInputSource> (url);
}

```

Function for writing (here is SDK check needed I think):

```auto
void EQ::_saveEqValsToFile() {
    if (_fileChooser != nullptr)
        return;
	
	// Here need to check what is current SDK version because this runtime permission
	// check can be done only for Android SDK versions 28 and lower
/*
    if (!RuntimePermissions::isGranted(RuntimePermissions::writeExternalStorage))
    {
        SafePointer<EQ> safeThis(this);
        RuntimePermissions::request(RuntimePermissions::writeExternalStorage,
            [safeThis](bool granted) mutable
            {
                if (safeThis != nullptr && granted)
                    safeThis->_saveEqValsToFile();
            });
        return;
    }
*/
    _fileChooser.reset(new FileChooser("Select a location or a file to save", File(), "*.mycoolformat"));

    _fileChooser->launchAsync(FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles | FileBrowserComponent::warnAboutOverwriting,
        [this](const FileChooser& fc) mutable {

            if (fc.getURLResults().size() > 0) {
                auto my_output_file = makeOutputStream(fc.getURLResult());

                std::string my_text = "Writing to files on Android makes me so happy.";
                my_output_file->writeString(my_text);
            }
            _fileChooser = nullptr;
        }, nullptr);
}

```

Read function:

```auto
void EQ::_loadEqValsFromFile() {
    if (_fileChooser != nullptr)
        return;

    if (!RuntimePermissions::isGranted(RuntimePermissions::readExternalStorage))
    {
        SafePointer<EQ> safeThis(this);
        RuntimePermissions::request(RuntimePermissions::readExternalStorage,
            [safeThis](bool granted) mutable
            {
                if (safeThis != nullptr && granted)
                    safeThis->_loadEqValsFromFile();
            });
        return;
    }

    _fileChooser.reset(new FileChooser("Select a file to read...", File(), "*.mycoolformat"));

    _fileChooser->launchAsync(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
        [this](const FileChooser& fc) mutable {

            if (fc.getURLResults().size() > 0) {

                auto my_input_file = makeInputSource(fc.getURLResult());
                auto my_input_stream = my_input_file->createInputStream();

                std::string my_text = my_input_stream->readString().toStdString();
            }
            _fileChooser = nullptr;
        }, nullptr);
}

```

---

<div class="post-metadata">

**Author:** ![aamf](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/aamf/32/19941_2.png) [@aamf](https://forum.juce.com/u/aamf)\
**Post date:** [January 17, 2023, 12:06am UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/6 "2023-01-17T00:06:45Z")

</div>

> [@ervoL](#):
>
> some sdk version check is needed

It seems so! Have you found a solution for this?

Mine is a simple modification to `juce_android_RuntimePermissions.cpp`. To the method `RuntimePermissions::isRequired (PermissionID permission)` I have added :

```auto
if (permission == RuntimePermissions::writeExternalStorage && getAndroidSDKVersion() > 28)
        return false;

```

Now I can check first `if (RuntimePermissions::isRequired (RuntimePermissions::writeExternalStorage))` before actually requesting permissions.

---

<div class="post-metadata">

**Author:** ![ervoL](https://avatars.discourse-cdn.com/v4/letter/e/9de0a6/32.png) [@ervoL](https://forum.juce.com/u/ervoL)\
**Post date:** [January 17, 2023, 8:32am UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/7 "2023-01-17T08:32:10Z")

</div>

Since several people asked me regarding this issue I’m posting here an example function for writing to files (although I removed some parts of the code that were strictly coupled to my use-case, I think function should work):

```auto
void EQ::_saveEqValsToFile() {
    if (_fileChooser != nullptr)
        return;

    bool askWritePermission = true;
#if JUCE_ANDROID
    char osVersion[PROP_VALUE_MAX+1];
    int osVersionLength = __system_property_get("ro.build.version.sdk", osVersion);
    int osVersion_int = 0;
    try {
        osVersion_int = std::stoi(osVersion);
    } catch (...) {
        // error management
    }
    if (osVersion_int > 28) askWritePermission = false;
#endif

    if(askWritePermission)
        if (!RuntimePermissions::isGranted(RuntimePermissions::writeExternalStorage))
        {
            SafePointer<EQ> safeThis(this);
            RuntimePermissions::request(RuntimePermissions::writeExternalStorage,
                                        [safeThis](bool granted) mutable
                                        {
                                            if (safeThis != nullptr && granted)
                                                safeThis->_saveEqValsToFile();
                                        });
            return;
        }

    _fileChooser.reset(new FileChooser("Select a location to save", File(), "*.json"));

    _fileChooser->launchAsync(FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles | FileBrowserComponent::warnAboutOverwriting,
        [this](const FileChooser& fc) mutable {

            if (fc.getURLResults().size() > 0) {

                try {
                    auto my_output_file = makeOutputStream(fc.getURLResult());
                    auto my_text = _eqValues.toString();
                    my_output_file->writeString(my_text);
                }
                catch (...) {
                    // error management
                }
            }
            _fileChooser = nullptr;
        }, nullptr);
}

```

And a short side-note: JUCE is a nice framework and JUCE apps run fine on desktop PCs, but there are serious issues when it comes to i.e. Android support. JUCE is not 100% cross platform as advertised, at least when it comes to Android, so some workarounds are often needed and some new and purely Android features are not directly supported like i.e. advanced notifications or controls so just keep that in mind when developing a JUCE app for Android 🍻

---

<div class="post-metadata">

**Author:** ![aamf](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/aamf/32/19941_2.png) [@aamf](https://forum.juce.com/u/aamf)\
**Post date:** [January 17, 2023, 11:37am UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/8 "2023-01-17T11:37:42Z")

</div>

Yes, that’s true.  
Thanks for sharing!

---

<div class="post-metadata">

**Author:** ![aamf](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/aamf/32/19941_2.png) [@aamf](https://forum.juce.com/u/aamf)\
**Post date:** [January 18, 2023, 2:39pm UTC](https://forum.juce.com/t/problem-with-writeexternalstorage-runtime-permissions-on-android/53408/9 "2023-01-18T14:39:01Z")

</div>

I just discovered that one can check the API level simply by calling `android_get_device_api_level()`

```auto
#if JUCE_ANDROID
 if (android_get_device_api_level() <= 28)
#endif
 if (! RuntimePermissions::isGranted (RuntimePermissions::writeExternalStorage))
...

```
