2

[Rewrite, based on comments]

Simplified example:

rootUri is obtained by ACTION_OPEN_DOCUMENT_TREE. Then I create a folder (or more nested folders):

val rootTree = DocumentFile.fromTreeUri(context, rootUri) // rootTree.name == "treeRoot" 
val childTree = rootTree.createDirectory("ChildDir1") // childTree.name == "ChildDir1"
val childUriStr = childTree.uri.toString()

I store childUriStr into local database so I can later recreate childTree. childUriStr contains all elements needed: authority, rootTree documentId and childTree documentId. So childTree could be recreated by:

val childUri = Uri.parse(childUriStr)
val childTreeCopy = TreeDocumentFile(
                        DocumentFile.fromTreeUri(context, childTree), // equals rootTree
                        context,
                        childUri
                    )

But the TreeDocumentFile() constructor is package-private and only used internally and by DocumentFile.fromTreeUri().

Workaround 1 (slow):

var childTree = DocumentFile.fromTreeUri(context, childUri)!! // rootTree

// get subfolders
val subfolders = DocumentsContract.getDocumentId(childUri)
                     .substringAfter(
                         // root documentId
                         DocumentsContract.getDocumentId(childTree.uri))
                     ).trim('/').split('/')

// move down the folder structure
subfolders.forEach { childTree = childTree.findFile(it)!! }

// result
childTree

Workaround 2 (information about rootTree is lost):

val childAsRootUri = DocumentsContract.buildTreeDocumentUri(
                         childUri.authority,
                         DocumentsContract.getDocumentId(childUri)
                     )

var childAsRootTree = DocumentFile.fromTreeUri(context, childAsRootUri)!!

Here, the tree is planted at subfolder so I can not use childAsRootTree.parent, cannot resolve name childAsRootTree.name == null and probably some other hickups - definitely not recommended. DocumentFile.fromSingleUri() also loses too much functionality compared to TreeDocumentFile.

Workaround 3 (additional code to maintain):

Writing my own wrapper based on TreeDocumentFile

Use case (no need to read)

Please note that what is described above is a simplified example. A potential use case, similar to mine would be: User chooses a single file or a folder with multiple files via in-app file explorer (step 1). A root file of in-app file explorer is obtained by ACTION_OPEN_DOCUMENT_TREE. User can process the files and then the processed files are saved in the same directory if the user selected folder in step 1, or next to the selected file in a new folder if user selected file in step 1 (this requires accessing parent folder of the selected file). User can later decide to choose a different arrangement of saved files.

Jure Sencar
  • 554
  • 4
  • 13
  • What exactly do you mean by "The issue is that the function DocumentFile.fromTreeUri(context, uri) removes children and just keeps the root" ? What do you mean by "return TreeDocumentFile at child position" ? – CommonsWare Jan 20 '20 at 21:43
  • Hi. I added an example to clarify what I want. Subfolders downstream of tree get erased from `uri` in function `DocumentFile.fromTreeUri(context, uri)`, as the `DocumentFile.fromTreeUri(context, uri)` function internally calls `DocumentsContract.getTreeDocumentId(treeUri)`. – Jure Sencar Jan 20 '20 at 22:59
  • If `childUri` has what you want... what do you need `childTreeCopy1` or `childTreeCopy2` for? – CommonsWare Jan 20 '20 at 23:36
  • I want to store uri and then recreate `childTree` from `childUri` (e.g. after app restart), so my function can return DocumentFile as it contains methods such as `isFile()`, `isDirectory()`, `createFile()`, `canWrite()`, `exists()` etc. that are used throughout the app – Jure Sencar Jan 21 '20 at 00:18
  • Just save rootUri.toString(). – blackapps Jan 21 '20 at 08:23
  • `PS: Going through DocumentFile.findFile() is not an option due to bad performance.` . You have to. It makes no sense to save uris from files deep in the tree. You always have te start from the uri obtained from ACTION_OPEN_DOCUMENT_TREE. But you dont have to use DocumentFile.findFile(). Indeed that is slow. Use DocumentsContract. – blackapps Jan 21 '20 at 08:27
  • `I save the Uri pointing to a subfolder of DocumentFile tree. `. That makes no sense. – blackapps Jan 21 '20 at 08:29
  • 1
    My recommendation is that you create a small project with some instrumented tests demonstrating this problem. Show that, given a `TreeDocumentUri`, you cannot re-create that `TreeDocumentUri` from the `String` representation of the underlying `Uri`. Then, file an issue and hope that Google addresses it. In terms of a short-term fix... you may be able to create a replacement `fromTreeUri()` function, if you place that function in the `androidx.documentfile.provider` package, so you can access the package-private `TreeDocumentUri` constructor. – CommonsWare Jan 21 '20 at 12:06
  • You probably meant `TreeDocumentFile` instead of `TreeDocumentUri`. Other than that I agree. Thank you for comments. – Jure Sencar Jan 21 '20 at 13:15

2 Answers2

1

Try this.

    fun fromSubDirectoryUri(context: Context, uri: Uri): DocumentFile? {
        val documentId = DocumentsContract.getDocumentId(uri)
        return DocumentsContract.buildTreeDocumentUri(uri.authority, documentId)
            ?.let { DocumentFile.fromTreeUri(context, it) }
    }

It worked in my environment. However, I'm not sure if it will work in other environments because I couldn't find the official docs on developer.android.com.

0

Creating DocumentFile using reflection works for me since Android 5

fun fromTreeUri(context: Context, uri: Uri?): DocumentFile? {
    var result: DocumentFile? = null
    try {
        val c = Class.forName("androidx.documentfile.provider.TreeDocumentFile")
        val constructor = c.getDeclaredConstructor(
            DocumentFile::class.java,
            Context::class.java,
            Uri::class.java
        )
        constructor.isAccessible = true
        result = constructor.newInstance(null, context, uri) as DocumentFile
    } catch (ignored: Exception) {
    }
    return result
}
skvalex
  • 186
  • 1
  • 7