11

Scenario

I am trying to create many files, as a feature for the user. For example, I might write an app which creates one file for each song they listened to for the past few weeks (e.g. a text file with lyrics). I can't make the user pick the directory and filename for each file I generate, it would take them hours. The user should have access to these documents from other apps.

Solutions (not really)

In Android 11, it seems like Storage Access Framework is not going to be useful. I noticed there were 2 initially interesting options:

  1. Create a new file (which creates launches activity the user interacts with to save one file only), described here
  2. Grant access to a directory's contents (which allows read access but no way to write any documents), described here.
  3. Note: A solution already exists if you target Android 10 and request WRITE_EXTERNAL_STORAGE. Unfortunately this permission is denied on Android 11. Therefore, I cannot use the solution posted here.
  4. Get access to the directory using Storage Access Framework (specifically ACTION_OPEN_DOCUMENT_TREE) to get access to a directory, but I couldn't write files into this directory. (I get FileNotFoundException, as this is not a normal path, but a tree path.)

Verdict

I guess in my case, I need to go down the route of "manage all files on a storage device", as described here which involves launching an extra activity with ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION. I'd rather not ask permission to read the entire drive or make the user go into the settings app to give permission. Writing files shouldn't require any permission...

How would you handle saving many (non-media) files for the user?

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
  • use database to store your data and then allow user to "export" its content to separate file when needed? – Marcin Orlowski Nov 06 '20 at 19:48
  • does the user really need the files? Does he interact with those files? What is the reason for creating those files? – Lena Bru Nov 06 '20 at 19:48
  • @MarcinOrlowski: what if I wanted to export the database into 10 files with different information? The user has to use SAF 10 times? I think this is unreasonable. – Ben Butterworth Nov 06 '20 at 20:16
  • @LenaBru: There are many use cases for saving "many" files, for example, a NLP app might want to create a text file for each picture, describing its contents. A music app might want to write separate lyrics. It is also useful for debugging certain types of apps, where we want to see that a file was saved in a good format. On Android 11, you can't see into app specific directories (internal or external), even using `adb shell` or Android Studio File Explorer. These files/ documents can be used by the user as files, shared, organised in folder, opened with other apps. *Don't forget about files.* – Ben Butterworth Nov 06 '20 at 20:17
  • `it seems like Storage Access Framework is not going to be useful.` Well it is. You only have to let the user choose one directory once and you are done for the life time of your app. – blackapps Nov 06 '20 at 20:23
  • Granted, my challenge is not a challenge faced by the apps that are most popular today, which hide files from the user. Having said that, Google have introduced the Files app, so I would say files are still pretty important. – Ben Butterworth Nov 06 '20 at 21:35
  • `Get access to the directory using Storage Access Framework (specifically ACTION_OPEN_DOCUMENT_TREE) to get access to a directory, but I couldn't write files into this directory. ` Then you did something wrong as after that you can create folders and files in it for the lifetime of your app. – blackapps Nov 06 '20 at 22:19
  • You can use ACTION_OPEN_DOCUMENT_TREE for writing multiple files! See [this answer](https://stackoverflow.com/a/26765884/11535462) – Artem Odnovolov Feb 22 '21 at 21:27
  • Read this: https://stackoverflow.com/a/66366102/9917404 – Thoriya Prahalad Feb 25 '21 at 09:59

2 Answers2

16

There are 2 methods, but first, I'll define the file and directory name, which I will later save inside the external (sdcard) Downloads folder

val outputFilename = "my_file"
val outputDirectory = "my_sub_directory" // The folder within the Downloads folder, because we use `DIRECTORY_DOWNLOADS`

The working, but deprecated method:

Although the Environment.getExternalStoragePublicDirectory was deprecated, it still works on apps targeting and running on Android 11.

file = File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_DOCUMENTS),
    "$outputDirectory/$outputFilename"
)
val outputStream = FileOutputStream(file)

The MediaStore way:

Alternatively, you could use MediaStore's Files collection, suggested Gaurav Mall here. I didn't know about the MediaStore files collection...

I rewrote it in kotlin and modified it for writing files here:

val resolver = context.contentResolver
val values = ContentValues()
// save to a folder
values.put(MediaStore.MediaColumns.DISPLAY_NAME, outputFilename)
values.put(MediaStore.MediaColumns.MIME_TYPE, "application/my-custom-type")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + outputDirectory)
val uri = resolver.insert(MediaStore.Files.getContentUri("external"), values)
// You can use this outputStream to write whatever file you want:
val outputStream = resolver.openOutputStream(uri!!)
// Or alternatively call other methods of ContentResolver, e.g. .openFile

In both cases below, you don't need WRITE_EXTERNAL_STORAGE for Android 29 and above.

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
  • After digesting this problem, I think another good example (but for images) is [here](https://stackoverflow.com/a/59536115/7365866). – Ben Butterworth Nov 07 '20 at 23:40
  • The MediaStore solution is great, because it's easy to use, not confusing and actually works. Confirmed on Android 10+. Thank you. – Akito Nov 02 '21 at 22:19
-2

On Android 11 you can create a subdirectory in public directory Documents using the standard File classes as you could in below 10. And also in 10 if you knew how. Then create your files in that subdirectory. No fuss.

You have write access to a lot of other public directories too.

You do not need all files access for this.

The files you create in this way are accessable by the user using the official Files app on the device.

blackapps
  • 8,011
  • 2
  • 11
  • 25
  • Have you done this on an app which targets Android 11? `getExternalStoragePublicDirectory` was deprecated, they recommend using MediaStore, `Intent#ACTION_OPEN_DOCUMENT` or `Context#getExternalFilesDir(String)`. None of these options are viable for me, as I explained in the question. Therefore, I don't `have write access to a lot of other public directories too.` (Sure, I still have write access to MediaStore, but I'm writing documents, and the docs have stated I need to use SAF for it) – Ben Butterworth Nov 06 '20 at 21:30
  • For more explanation as to why your method won't work, read https://stackoverflow.com/a/58575031/7365866 and its comments – Ben Butterworth Nov 06 '20 at 21:47
  • 1
    All what i said works targetting Android 11 api 30. Just try and you will see. That functions are deprecated does not mean they do not work. – blackapps Nov 06 '20 at 22:17
  • `Context#getExternalFilesDir(String). None of these options are viable for me, as I explained in the question. ` ??? There is nothing in your post about getExternalFilesDir nor that you would have explained anything about using it. – blackapps Nov 06 '20 at 22:26
  • `I am trying to many small files, as a feature for the user.` The external files directory is still internal to the app, and only located on external storage. Therefore, it won't be useful as a feature for the user. (Not user accessible) – Ben Butterworth Nov 06 '20 at 22:33
  • Of course it is accessable for the user. Whatever you mean with accessable. The user can use the Files app to see and get all files if that is what you are after. – blackapps Nov 06 '20 at 22:37
  • have you actually got this working before on an app targeting Android 11 running on 11? – Ben Butterworth Nov 06 '20 at 22:46
  • Also, about `getExternalFilesDir`, they are not accessible to the user. Please read https://developer.android.com/reference/android/content/Context#getExternalFilesDir(java.lang.String) `These files are internal to the applications, and not typically visible to the user as media.` With Android 11, it cannot even be seen on through adb or file explorers. It seems to me like you don't know what you're talking about. – Ben Butterworth Nov 06 '20 at 22:49
  • `they are not accessible to the user.` It is unclear to me which action the user is doing to see those files. And as said before: All files your app creates in its getExternalFilesDIr are visible to the user using the official FIles app on the Android 11 device. – blackapps Nov 06 '20 at 22:59
  • No they're not. Clearly you don't know what you're talking about. Here is a screenshot, API 29 (10) on left, API 30 on right. External files directory (in the sdcard, located in Android/data), is showing empty on API 30. https://i.imgur.com/khceZZo.png – Ben Butterworth Nov 06 '20 at 23:07
  • I have no device and all i tell is like found on Pixel 3 Xl emulator api 30. – blackapps Nov 06 '20 at 23:17
  • Anyhow, if you create a folder in Documents and write files in them they are visible by Files app too. Please check. I just created an R emulator. There the ../Android/datafolder is not visible indeed. But files created in Documents are. – blackapps Nov 06 '20 at 23:22
  • Yes, but to write these files in Documents (or anywhere outside MediaStore) on Android 11, it seems to me I need to use storage access framework **per file**, or use **All Files Access**. Both are not good options. However, i found https://stackoverflow.com/a/57649669/7365866 which may allow me to contribute documents into the MediaStore – Ben Butterworth Nov 06 '20 at 23:27
  • No. You need nothing of that all.Reread my answer! You just can write using the FIle class and such to the Documents, Pictures, Alarms an more public directories. This is the third or fourth time i tell you so. It is time you start coding. – blackapps Nov 06 '20 at 23:34
  • And how do you get those paths? The methods to actually get those paths don't work on Android 11. Maybe you can share some code – Ben Butterworth Nov 06 '20 at 23:56
  • Which methods ar you talking about? `Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)` gives you a nice `File` object and full path. Deprecated ? Yes indeed. Does that matter? No. – blackapps Nov 06 '20 at 23:59
  • It works... Thank you! `getExternalStoragePublicDirectory` is deprecated but it works. The comments above the method definition stated: `the path returned from this method is no longer directly accessible to apps.`, but it works for writing new files at least. (didn't test for other use cases) – Ben Butterworth Nov 07 '20 at 00:36
  • @Ben Butterworth, Please dont edit my answers. Use comments if you have anything to add. – blackapps Nov 07 '20 at 09:51