How to use external public storage directory in Android Q and above?

from the CommonsWare Community archives

At October 24, 2019, 7:26am, Shahood asked:

Hi,

Currently, I’m saving files to /storage/emulated/0/AppName/Media and sub-folders like /Images, /Audio etc. using the following code:

File root = getExternalStorageDirectory();
File dir = new File(root.getAbsolutePath() + "/AppName/Media/Images");
if (dir.exists() || dir.mkdirs()) {
    File file = new File(dir, filename);
    return Uri.fromFile(file);
}

Is there a way to get the same path in Android Q? If yes, how?

My main objective is to keep the files publicly available and also that these are not deleted on app uninstall.

I went through your SO answer but couldn’t really figure how MediaStore.MediaColumns.RELATIVE_PATH really works and whether it would help achieving the required path or not.

Thanks!


At October 24, 2019, 11:09am, mmurphy replied:

No. Or, rather, you can get the path the same way that you are now, but you cannot read or write at that location.

Use the Storage Access Framework and allow the user to decide where the user wants the user’s content to go.

No, as RELATIVE_PATH only works under standard image roots. So, for example, Open Camera stores its photos in DCIM/OpenCamera/ in external storage. RELATIVE_PATH will let you store photos in such a directory, as DCIM/ is a recognized root for images. However, AppName/ is not.


At October 26, 2019, 12:05pm, Shahood replied:

What if I want to force the save location, pretty much how Whatsapp does: it saves the files in subfolders created at the location /storage/emulated/0/Whatsapp (don’t know where it saves in device with Q)?

My app intends to share data among its users via cloud storage but it downloads and stores the files on local storage before these are presented to the user. So, in cloud storage, I store download URL and filename. First, I download the file using the URL and then save it in local storage using a pre-determined folder and the filename as retrieved from the cloud. After the download, whenever the user opens the app and accesses the same received message with the file, it will be accessed from the local storage not the cloud. My ListAdapter curates the file path on the go using the filename and the pre-determined folder and presents it in the RecyclerView.

So, giving user the option to choose the download folder doesn’t seem appropriate in this scenario. I can ask the user to select a folder the first time he installs the app (though it seems pretty awkward) but what if he changes the location later on? How will the app come up with the file path on the fly in that case?

Ain’t there any option in Android Q to create a public folder in external storage and use it for the purposes of one’s app?

I hope I made my questions and objective clear.

Thanks


At October 26, 2019, 12:38pm, mmurphy replied:

Use getExternalFilesDir() as your base directory. Or, use getFilesDir(), if the user does not need independent access to this content.

That is only practical as a relative path from some root.

It is the user’s device. It is the user’s Internet connection. It is the user’s data. One would imagine that the user should get a vote where the user’s data, downloaded via the user’s Internet connection, gets stored on the user’s device.

I have no idea what “changes the location” means. You are certainly welcome to offer a way in your app for the user to choose some other location to place the content. Or, use something like getExternalFilesDir(), which is already tied to your app.

You would store the content in the location identified by the user. You would store that location in something like SharedPreferences.

Use getExternalFilesDir(). Or, use ACTION_OPEN_DOCUMENT_TREE to have the user choose a base location, into which you create your own document tree for the content that you are managing.

The technique that you describe — putting an app directory directly in the external storage root — has long been embarrassing. It’s the Android equivalent of dumping your stuff in the root of C: in Windows. We got away from that pattern in Windows a couple of decades ago, after countless user complaints. I have been advising developers to not do this for years, anticipating that we would need to stop doing that at some point. That “some point” is now.


At October 26, 2019, 1:13pm, Shahood replied:

That’s fine. But I believe that if we make user the selector of the content location, we should give him an option to change that location in future.
So, if he does change the location, how would the app know which files to look up in which folder, based only on the filename retrieved from the cloud?
Should I arrange to move all the content to the new location as soon as the user decides to change the location?


At October 26, 2019, 1:28pm, mmurphy replied:

That sounds wonderful!

As you suggest, you could move the content to the new location.

Or, you could keep a history of roots, checking them all, and preferring the newest root for any new content.

Or, you could keep more metadata on the server, holding onto a “root ID” in addition to a relative path, to hint where the client app should look for it among the historical set of roots.

There are probably other options as well — those are the ones that are coming to mind right now.


At October 26, 2019, 1:30pm, Shahood replied:

This would have to be done in case of each message for each user who has access to that message. So keeping track of the root folders of each user in a message sounds like a lot of work.

Other options are considerable though!