Release nb187: Copy a text file / image from assets to shared storage (→ Android 10+)

Here is a test app (→ Designer: DefaultFileScope = Shared)

with which a text file and an image can be copied from the assets to the shared storage (for devices with Android ≥ 10). This works neither for the text file nor for the image (regardless of whether storage permission was granted beforehand):
"Error 908: The permission WRITE_EXTERNAL_STORAGE has been denied. Please enable ...".

Note: With → Niotron the text file is copied to shared storage (without permission), but you need READ permission to read it. The image (foto.jpg) can, however, be copied into the shared storage and also displayed from there (without READ permission, as it should be). Folders (which function as shared storage and images can be copied into) are:

  • /Download
  • /Documents
  • /DCIM
  • /Pictures

See here.

If you try to delete these files, however, you will get an error message (incorrectly):
"Error 908: The permission WRITE_EXTERNAL_STORAGE has been denied. Please enable ...".

Steps to test the app (especially on Android 10 and 11):

  1. Install and open the app.
  2. Deny permission.
  3. Click first button (deny permission).
  4. Check the CheckBox and click on the first button angain.
  5. Click on the third button (Copy bg.jpg to ...).
  6. Click on the fourth button (files ... exist?).
  7. Click on the last button (remove ...).
  8. Uninstall and reinstall the app.
  9. Grant permission.
  10. Continue with step 3 - 6: → same result.

Try also different folders (→ TextBox).

Note:
If DefaultFileScope was set to → Shared, only READ permission is declared in the Manifest and not WRITE permission. But on devices with Android < 10 it must be possible to request WRITE permission.

So, as I already said, READ / WRITE permissions should be declared this way in the Manifest:

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

This is the same app with → Designer: DefaultFileScope = Legacy:

Repeat the above steps.
It should work only if READ permission has been granted.

Important note:
If the text file or image already exists and you try to copy it again, the files are not replaced, but a new file is created (e.g.: 123(1).txt). This causes the app to stop working after the app is uninstalled and reinstalled (because the text file is then not created by the app).

3 Likes

Is this not normal behavior? Windows does the same. I think the first thing to do is to check if the file exists, if it does exist to suggest the user to rename it.

Are you serious? :upside_down_face:

If I copy a file with TaifunFile or FileTools multiple times, the old file is simply replaced.

1 Like

I understand why this is happening. For shared scope, we make an insertion into the MediaStore, and when the name collides the MediaStore creates a copy. What we should probably be doing instead is testing to see if the file is already in the MediaStore and trying to open the existing record rather than creating a new one every time.

2 Likes

But why should the file be overwritten? All normal systems ask before replacing the file. In windows, try to copy the file with the same name. Windows will ask if the file should be overwritten.

This is normal behaviour in Android to overwrite the existing file....

2 Likes

As I alread said, if the file in not overwritten, the app will no longer work after the app in un-/reinstalled because the this file is then NOT created by the app and cannot be read / removed by the reinstalled (new) app.

1 Like

Yes, all components that can save something (SoundRecorder etc.) behave like that.

You're right. Docs suggest the same Access media files from shared storage  |  Android Developers

2 Likes

By the way, I've already mentioned that several times before:

Other problem. Why can't I use spaces in the names of files saved in the file component? I get an error:

Illegal character in path at index 90.

Where 90 is the index space in the path.

When I used that filename:

two files appeared in the "files" directory. One with the name "my file app inventor.txt" but empty, the other "my%20file%20app%20inventor.txt" with the contents saved 12345.

1 Like

Have you tried with full path?

Perhaps the problem is in the function that generates a full path. Skipping this step might solve it

This topic is about Shared and Android 10+, but I wanted to test Shared on Android 9 and I used this code.

It gets the path
file:///storage/emulated/0/Download

But when making a list, I get the list of files of the SdCard.

Does the same happen on Android 10+?

shared_and9.aia (1.9 KB)

AppInventor should really have a get ASD path block by default....

2 Likes

Yes, in companion on Android 10, every file and directory under /storage/emulated/ - it includes the /0 directory - is returned

On Andriod 11 using companion, it returns the Download directory only, full paths for every file.

1 Like

Here it is:

1 Like

2 Likes

I would like to introduce these three basic terms:

Absolute path │ relative path │ full path

1. This path is an → absolute path:
/storage/emulated/0/Android/data/packageName/files/

2. and this path is a → relative path:
/Android/data/packageName/files

Some components need a relative and others an absolute path.

3. And on top of that, some components or Android versions require a → full path:
file:///storage/emulated/0/Android/data/packageName/files/

I recommend these three terms to distinguish the paths, for example:

  • relative path: /Download
  • absolute path: /storage/emulated/0/Download
  • full path: file:///storage/emulated/0/Download

I'll add this to my guide Some basics on Android storage.

3 Likes

AppInventor should really have a get ASD path block by default.

Clever, but obscure for most users :wink: - not now you have shown how to do it. :smiley: