DocumentFile.exists() on a path that doesn't yet exist always returns true due to DocumentFile().fromTreeUri()
Asked Answered
A

3

7

I'm trying to check if a file exists prior to creating it, using DocumentFile (due to the Storage Access Framework). But DocumentFile().fromTreeUri() removes the non-existant portion of the would-be Uri, which then causes DocumentFile().exists() to always return true, regardless of whether it exists or not.

I've created a simple example to demonstrate my point. First we ask the user to select a directory:

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // Ask the user for the source folder
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, 100);
}

On response, we add /fictionalFile to the path (thus making it a non-existent file), and then check if it exists:

public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
    if (resultCode == RESULT_OK)
    {
        if(requestCode == 100)
        {
            Uri fictionalURI = Uri.parse(resultData.getData()+"/fictionalFile");
            DocumentFile fictionalFile = DocumentFile.fromTreeUri(this, fictionalURI);
            Log.i("STORAGE", "FICTIONAL URI: "+fictionalURI);
            Log.i("STORAGE", "FICTIONAL DOCUMENTFILE URI: "+fictionalFile.getUri());

            if(fictionalFile.exists())
            {
                Log.i("STORAGE", "Fictional file exists");
            }
        }
    }
}

However, when DocumentFile.fromTreeUri() is run on the fictional Uri, the fake "/fictionalfile" portion is lost, which then causes the DocumentFile.exists() function to return true, as shown by the below LogCat:

I/STORAGE: FICTIONAL URI: content://com.android.externalstorage.documents/tree/17FA-1C18%3AFileSync%2Ftarget/fictionalFile

I/STORAGE: FICTIONAL DOCUMENTFILE URI: content://com.android.externalstorage.documents/tree/17FA-1C18%3AFileSync%2Ftarget/document/17FA-1C18%3AFileSync%2Ftarget

I/STORAGE: Fictional file exists

(In the above example, I'm using the SD card, thus the long path names)

Is there another way to check if a not-yet created DocumentFile exists? The use case is that when copying a file from Directory A to Directory B, I want to check if said file already exists in Directory B prior to starting the transfer.

UPDATE: I've now realised that using DocumentFile.fromTreeUri() is wrong, and that I should be using DocumentFile.fromSingleUri(). This helps, but upon running .exists() on the new file I am getting W/DocumentFile: Failed query: java.lang.UnsupportedOperationException: Unsupported Uri content://com.android.externalstorage.documents/tree/17FA-1C18%3AFileSync%2Ftarget/fictionalFile. Any thoughts?

public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
    if (resultCode == RESULT_OK)
    {
        if(requestCode == 100)
        {
            Uri fictionalURI = Uri.parse(resultData.getData()+"/fictionalFile");
            DocumentFile fictionalFile = DocumentFile.fromSingleUri(this, fictionalURI);
            Log.i("STORAGE", "FICTIONAL URI: "+fictionalURI);
            Log.i("STORAGE", "FICTIONAL DOCUMENTFILE URI: "+fictionalFile.getUri());

            if(fictionalFile != null && fictionalFile.exists())
            {
                Log.i("STORAGE", "Fictional file exists");
            }
        }
    }
}
Apogamy answered 2/8, 2018 at 20:6 Comment(5)
You can use File class and is method exists(). docs.oracle.com/javase/7/docs/api/java/io/File.html#exists()Verditer
I think I'm unable to use File due to it being a not-yet-existent Uri that was created using SAF?Apogamy
I have updated the OP as I have partially fixed it, but not quite!Apogamy
If you change ACTION_OPEN_DOCUMENT_TREE by ACTION_OPEN_DOCUMENTVerditer
The use case in question requires a source folder to be selected (it's to perform a copy/paste of a directory and its contents)Apogamy
B
7

Given treeUri as being the Uri returned by ACTION_OPEN_DOCUMENT_TREE, wrap treeUri in a DocumentFile using fromTreeUri(), then call findFile() on that DocumentFile supplying the display name that you are looking for (e.g., fictionalFile). If it returns null, there is no file matching that display name.

IOW:

if (DocumentFile.fromTreeUri(this, treeUri).findFile(whatevs) == null) {
  // TODO: something
}

Note, though, that "display name" is not necessarily a filename.

Bechler answered 2/8, 2018 at 21:55 Comment(14)
But Uri.findFile() is not a function, best I can tell?Apogamy
@DuncanMcArdle: Whoops, sorry -- I forgot the steps to create the DocumentFile.Bechler
CommonsWare, whilst playing around inside Android Studio, it suggested .findFile() as a function of DocumentFile. This led to me realising that a workable solution is to use DocumentFile targetFile = DocumentFile.fromTreeUri(this, treeUri).findFile("fileName"); and then targetFile == null as an alternative to the (in my case) unreliable DocumentFile.exists()! If you'd be willing to change your answer to reflect this, I'd be happy to accept it as the solution, as I believe you were close enough to the mark. Thanks for your help!Apogamy
But what about fictionalFile(1), fictionalFile(2) ...Bosco
@user9717489: Sorry, but I do not know what you mean.Bechler
@Bechler when a file already exists with a similar name then SAF automatically adds (1), (2).. and so on. In that case, find file won't workBosco
@Bechler Will it work for folder as well ? Suppose if I want to check whether that folder exist or not ?Anelace
@Smeet: I suspect that it works, but I have not tried it personally. Testing is highly recommended! 😁Bechler
@Bechler I tested just now, it is not working. I am using solution from #37966886Anelace
@Smeet: I would not expect the approach outlined in that answer to work. Never attempt to hand-assemble a document ID.Bechler
@Bechler Yes correct. I have one question, DocumentFile.fromSingleUri() returns me the folder uri, now I want to check whether particular file exist or not, how can I do ? If I use findFile, it will through exception as I can not use this method on SingleDocumentFileAnelace
@Bechler I have folder Uri, and I want to check whether particular file exist in that folder or not. I am really appreciating your all answers on SO regarding SAF, its really confusing and due to Android 11 this is only an option for complex task.Anelace
@Smeet: "DocumentFile.fromSingleUri() returns me the folder uri" -- you should be using fromTreeUri(), not fromSingleUri(), if you know that the Uri points to a tree.Bechler
@Bechler Yes I check that method - fromTreeUri() but it returns the Uri of rootPath not the child folders. Don't know why.Anelace
C
1

In my case, when I checked DocumentFile.fromSingleUri(context, media.fileUri)?.exists() == true in CoroutineWorker with applicationContext, DocumentProvider always returned true, when file was deleted from outside application, for example, in file manager.

The problem was solved with uri.isFileExist(context)

import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile

fun DocumentFile?.isFile() = this?.isFile ?: false

fun DocumentFile?.isExists() = this?.exists() ?: false

fun DocumentFile?.getLength() = this?.length() ?: 0

fun DocumentFile?.isFileExist() = isFile() && isExists() && getLength() > 0

fun Uri.isFileExist(context: Context) =
    DocumentFile.fromSingleUri(context, this).isFileExist()

UPDATE: The solution below works correctly on android 10+. I ended up replacing it to get it working properly on all versions of android.

fun Uri.isFileExist(context: Context): Boolean {
    var isExist = false
    runCatching {
        context.contentResolver.openInputStream(this)?.let {
             it.close()
             isExist = true
        }
    }
    return isExist
}
Cingulum answered 20/8, 2020 at 14:50 Comment(2)
This only work on Android 10+, but not prior to it. Anyway method exist() also work well on Android 10+ so it is not necessary to check more fields. It apprears to be that exists method doesn't work in older API Levels and neither does the rest of fields nor therefore your solution. Please recheck just in case you missed something. Specifically i have tested on Android 10, 8.1, 5 and 4.4, not 9. There's a chance it also works on 9, but definitely not for 8.1 or less.Faltboat
Ah, I forgot to comment that it won't work when using DocumentFile.fromSingleUri but it will with DocumentFile.fromFile on Android 9-, but again, your snippet wont be necesarry. Just use exists method. In summary use DocumentFile.fromSingleUri(...).exists() on Android 10 and DocumentFile.fromFile(...).exists() on Android 9-.Faltboat
R
0

DocumentFile.fromSingleUri worked for me. However, I want to create a new file if the file does not exist.

Hopefully this solves your purpose at least.

Uri fileUri = DocumentsContract.buildDocumentUriUsingTree(Prefs.externalURI, filePath);
if(DocumentFile.fromSingleUri(this, fileUri).exists()) {
  // do some stuff
}
Rebuild answered 7/5 at 9:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.