0

As of API 29 Environment.getExternalStorageDirectory() is deprecated.

Earlier files were stored in a custom folder with path : /storage/emulated/0/customFolder

What I want is to save all files related to my app in this custom folder.

Is it advisable to use this way :

      val dir = File("/storage/emulated/0/customFolder")

Or can I use like this :

    val listDir = getExternalFilesDirs(null)
    val a =  listDir[0].toString()
    val p = a.splitToSequence("/")
    val h = "${p.elementAt(0)}/${p.elementAt(1)}/${p.elementAt(2)}/${p.elementAt(3)}"
    println(h) // this would return -  /storage/emulated/0
    val customFolder = File("$h/customFolder")

I have read about methods like getExternalFilesDir(), getFilesDir(), but using these won't satisfy my need as "files will be deleted when the application is uninstalled".

mad_lad
  • 654
  • 3
  • 8
  • 20

3 Answers3

0

As of API 29 Environment.getExternalStorageDirectory() is deprecated.

More importantly, it does not work. You cannot write to arbitrary locations on external storage on Android 10 and higher using filesystem APIs.

What I want is to save all files related to my app in this custom folder

That will not be possible, sorry.

What you can do is use the Storage Access Framework's ACTION_OPEN_DOCUMENT_TREE to allow the user to choose where on the user's device you should be storing the user's content.

Is it advisable to use this way :

No, because you cannot write to that location.

Or can I use like this :

No, because you cannot write to that location.

Temporarily, you can use android:requestLegacyExternalStorage="true" to disable the "scoped storage" change in Android 10 and allow your app to continue using Environment.getExternalStorageDirectory(). That will stop working on Android 10 devices as soon as your targetSdkVersion hits 29, which you will need to do later this year to ship your app on the Play Store and certain other distribution channels.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
0

Scoped storage wont allow you to access files via java.io.File, unless android.permission.MANAGE_EXTERNAL_STORAGE is granted. To enable you access custom folders without this permission, you can use SimpleStorage:

// => /storage/emulated/0/MyFolder
val fileFromExternalStorage = DocumentFileCompat.fromSimplePath(context, basePath = "MyFolder")

// => /storage/9016-4EF8/MyFolder
val fileFromSdCard = DocumentFileCompat.fromSimplePath(context, storageId = "9016-4EF8", basePath = "MyFolder")
Anggrayudi H
  • 14,977
  • 11
  • 54
  • 87
0

Tested on :

  1. Xiaomi M2102J20SI
  2. Emulator Pixel 4 XL API 30

Function askPermission() opens the target directory.

@RequiresApi(Build.VERSION_CODES.Q)
private fun askPermission() {
    val storageManager = application.getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val intent =  storageManager.primaryStorageVolume.createOpenDocumentTreeIntent()

    val targetDirectory = "WhatsApp%2FMedia%2F.Statuses" // add your directory to be selected by the user
    var uri = intent.getParcelableExtra<Uri>("android.provider.extra.INITIAL_URI") as Uri
    var scheme = uri.toString()
    scheme = scheme.replace("/root/", "/document/")
    scheme += "%3A$targetDirectory"
    uri = Uri.parse(scheme)
    intent.putExtra("android.provider.extra.INITIAL_URI", uri)
    startActivityForResult(intent, REQUEST_CODE)
}

Uri of the file will be returned in onActivityResult()

 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
            if (data != null) {
                data.data?.let { treeUri ->

                    // treeUri is the Uri of the file
                    
                   // if life long access is required the takePersistableUriPermission() is used

                    contentResolver.takePersistableUriPermission(
                            treeUri,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION or
                                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    )

                  readSDK30(treeUri)
                }
            }
        }
    }

Function readSDK30() is used to read files & folders from Uri

  private fun readSDK30(treeUri: Uri) {
        val tree = DocumentFile.fromTreeUri(this, treeUri)!!

        thread {
            val uriList  = arrayListOf<Uri>()
            listFiles(tree).forEach { uri ->
                 
                // Collect all the Uri from here
            }
            
        }
    }

Function listFiles() returns all the files & folders in the given Uri

fun listFiles(folder: DocumentFile): List<Uri> {
            return if (folder.isDirectory) {
                folder.listFiles().mapNotNull { file ->
                    if (file.name != null) file.uri else null
                }
            } else {
                emptyList()
            }
        }
mad_lad
  • 654
  • 3
  • 8
  • 20