user8660130
user8660130

Reputation:

How to access files in a directory given a content URI?

My question is very similar to this question. I got content Uri of a directory using ACTION_OPEN_DOCUMENT_TREE. and got something like this

content://com.android.externalstorage.documents/tree/primary%3ASHAREit%2Fpictures

as result upon selecting a directory. Now, my problem is how can i access all the files inside the directory (and preferably subdirectories too).

Upvotes: 7

Views: 5490

Answers (3)

CommonsWare
CommonsWare

Reputation: 1007534

Use DocumentFile.fromTreeUri() to create a DocumentFile for your tree. Then, use listFiles() to get a list of the documents and sub-trees inside of that tree. For those where isDirectory() returns true, you can further traverse the tree. For the rest, use getUri() to get a Uri to the document, which you can use with openInputStream() on a ContentResolver to get the content, if needed.

Upvotes: 13

Ben Butterworth
Ben Butterworth

Reputation: 28968

I am very new to Android Development, and was stuck on understanding CommonsWare's answer (among other resources) for a long time. This resource was also helpful.

  1. You need the DocumentFile AndroidX library, so add that to your build.gradle:
implementation "androidx.documentfile:documentfile:1.0.1"
  1. Once you've gotten your content URI which contains the tree, you can use DocumentFile.fromTreeUri
        val filenamesToDocumentFile = mutableMapOf<String, DocumentFile>()
        val documentsTree = DocumentFile.fromTreeUri(context, treeUri) ?: return
        val childDocuments = documentsTree.listFiles()
        for (childDocument in childDocuments) {
            childDocuments[0].name?.let {
            filenamesToDocumentFile[it] = childDocument
            }
        }

Now I'm off to figure out how to use this DocumentFile... (hint: val inputStream = contentResolver.openInputStream(childDocument.uri))

Upvotes: 6

fireb86
fireb86

Reputation: 1872

Here a snippet related to @CommonsWare answer.

Launch the file picker with Intent.ACTION_OPEN_DOCUMENT_TREE

startActivityForResult(
    Intent.createChooser(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), "Choose directory"),
    IMPORT_FILE_REQUEST
)

Receive the Uri of the selected directory from file picker

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when (requestCode) {
        IMPORT_FILE_REQUEST -> {
            if (resultCode != Activity.RESULT_OK) return
            val uri = data?.data ?: return
            // get children uri from the tree uri
            val childrenUri =
                DocumentsContract.buildChildDocumentsUriUsingTree(
                    uri,
                    DocumentsContract.getTreeDocumentId(uri)
                )
            // get document file from children uri
            val tree = DocumentFile.fromTreeUri(this, childrenUri)
            // get the list of the documents
            tree?.listFiles()?.forEach { doc ->
                // get the input stream of a single document
                val iss = contentResolver.openInputStream(doc.uri)
                // prepare the output stream
                val oss = FileOutputStream(File(filesDir, doc.name))
                // copy the file
                CopyFile { result ->
                    println("file copied? $result")
                }.execute(iss, oss)
            }
        }
    }
}

Copy file with AsyncTask (feel free to use threads, coroutines..)

class CopyFile(val callback: (Boolean) -> Unit) :
    AsyncTask<Closeable, Int, Boolean>() {
    override fun doInBackground(vararg closeables: Closeable): Boolean {
        if (closeables.size != 2) throw IllegalArgumentException("two arguments required: input stream and output stream")
        try {
            (closeables[0] as InputStream).use { iss ->
                (closeables[1] as OutputStream).use { oss ->
                    iss.copyTo(oss)
                    return true
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    override fun onPostExecute(result: Boolean) {
        callback.invoke(result)
    }
}

Upvotes: 4

Related Questions