4

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?

u2gilles
  • 6,888
  • 7
  • 51
  • 75
  • 1
    Just create your own directory in the Download folder first. Then write your file to your directory. All in the old way. – blackapps Jan 15 '21 at 22:18
  • 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? – u2gilles Jan 16 '21 at 00:34
  • Meanwhile you will have tried all so please report. – blackapps Jan 16 '21 at 08:43

1 Answers1

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://stackoverflow.com/a/64192581/4252352 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.

Mark
  • 9,604
  • 5
  • 36
  • 64
  • 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. – blackapps Jan 15 '21 at 23:27
  • 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? – Mark Jan 15 '21 at 23:38
  • 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. – blackapps Jan 16 '21 at 00:05
  • Thanks guys, I'll play with this tomorrow. – u2gilles Jan 16 '21 at 00:39
  • 1
    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. – Nahid Hasan May 08 '21 at 06:18
  • @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` ? – JustAMartin Nov 07 '22 at 15:48
  • @mark How to create a sub directory inside shared public storage like download folder ? – DevMobApp Dec 28 '22 at 08:48