Android R – Create a text file in Download folder using Scoped storage (MediaStore.Downloads)
Asked Answered
S

1

5

My app needs to save a string in a text file in Download folder. Currently (target: API 29 (Q) I am using the FILE API with :

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

But for API 30 (R), if I understood well, I have to migrate this to Scoped storage (MediaStore.Downloads).

And here, I am a bit lost. I can’t find good documentation or snippet showing how to create a text file in Download. I would appriciate if someone could explain or show how to do this?

Semiquaver answered 15/1, 2021 at 21:45 Comment(3)
Just create your own directory in the Download folder first. Then write your file to your directory. All in the old way.Linson
Thanks I will try this solution but I am still a bit confused. Do you mean that if I write in /storage/emulated/Download directly, it will fail? Should the directory be created programmatically from my app? May I used standard java lib to create the directoy?Semiquaver
Meanwhile you will have tried all so please report.Linson
O
5

Having just tested using your above setup in a AndrioidManifest.xml :

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

With those permissions granted and on a emulator running API 30 (R) I was able to write/read/update a text file with this simple code :

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    
    if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
        val f = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "new_file.txt")
        f.appendText("test ${Instant.now().toEpochMilli()}\n")

        f.readLines().forEach { line -> Log.e("LOG", line)}
    }
}

This post : https://mcmap.net/q/89290/-write-permissions-not-working-scoped-storage-android-sdk-30-aka-android-11 suggests that if you created the file you will be able to have access to it using the File api. Note : Environment.getExternalStoragePublicDirectory is deprecated but seems to work even on Android 11 if you are the owner of the file.

Just for fun I swapped between target and compile versions 29/30 to see if anything would blow up when targetting different sdk's and reinstalling the same app on the same emulator. It worked fine, I had full access to the same file regardless.

If I'm honest the whole thing is a bit of a shambles - this post by CommonsWare https://commonsware.com/blog/2019/12/21/scoped-storage-stories-storing-mediastore.html is a good read as it touches on many things that are now enforced in Android 11, even though it mostly talks about Android 10.

Documentation seems to be splintered with sections pertaining to storage / scoped storage and the like in a lot of different places. This link to a table gives a good launch ground for sifting through the documentation based on inital use case : https://developer.android.com/training/data-storage

I have also attached a screenshot of the file appearing in the file manager :

File Manager

PS : Terrible throwaway code here - I/O work on main thread etc .. only for illustrative purposes.

Based on comment "R != 30". My usages of "R" and Api "30" are based on this from the AndroidStudio IDE :

Andriod versions

(R = runtime environment, Api 30 = sdk for runtime)

Happy for an edit if I have misunderstood something, or not semantically correct in some way.

Ornament answered 15/1, 2021 at 22:45 Comment(7)
R != 30. R behaves more like 29. There is 29, R and 30. android:requestLegacyExternalStorage="true" only works for 29 and has no effect on an Android 11 device.Linson
Sure, in Android 11 android:requestLegacyExternalStorage="true" is automatically false - in my answer I was swapping between api 29 and 30 so hence why it was included, and was a direct take from the OP's setup. Not sure what R != 30 means - whenever I create a virtual device it clearly states as such?Ornament
No it is not false. It has no effect. And R!=30 means that R not is equal to 30. There is and i repeat: 29, R and 30.Linson
Thanks guys, I'll play with this tomorrow.Semiquaver
Scoped storage enforcement Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.Krystlekrystyna
@NahidHasan But what about the reverse? We have an app that targets A11 (API 30) and runs on A10. I see it's crashing because we are trying to use the old approach with getExternalStoragePublicDirectory(ourCustomFolder). So do we have to check for Build.VERSION.SDK_INT >= Build.VERSION_CODES.R or Q to know when we should use Environment.DIRECTORY_DOCUMENTS with targetSdkVersion 30 ?Sigrid
@mark How to create a sub directory inside shared public storage like download folder ?Windowpane

© 2022 - 2024 — McMap. All rights reserved.