6

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");
            }
        }
    }
}
Community
  • 1
  • 1
Duncan McArdle
  • 501
  • 5
  • 14

2 Answers2

6

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • But `Uri.findFile()` is not a function, best I can tell? – Duncan McArdle Aug 03 '18 at 07:39
  • @DuncanMcArdle: Whoops, sorry -- I forgot the steps to create the `DocumentFile`. – CommonsWare Aug 03 '18 at 10:28
  • 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! – Duncan McArdle Aug 03 '18 at 15:56
  • But what about fictionalFile(1), fictionalFile(2) ... – Yash Arora Feb 14 '21 at 13:29
  • @user9717489: Sorry, but I do not know what you mean. – CommonsWare Feb 14 '21 at 13:35
  • @CommonsWare 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 work – Yash Arora Feb 14 '21 at 13:47
  • @CommonsWare Will it work for folder as well ? Suppose if I want to check whether that folder exist or not ? – Smeet May 13 '21 at 12:45
  • @Smeet: I suspect that it works, but I have not tried it personally. Testing is highly recommended! – CommonsWare May 13 '21 at 12:49
  • @CommonsWare I tested just now, it is not working. I am using solution from https://stackoverflow.com/questions/37966386/saf-documentfile-check-if-path-exists-without-creating-each-documentfile-at-ea – Smeet May 13 '21 at 13:10
  • @Smeet: I would not expect the approach outlined in that answer to work. **Never** attempt to hand-assemble a document ID. – CommonsWare May 13 '21 at 13:28
  • @CommonsWare 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 SingleDocumentFile – Smeet May 13 '21 at 14:11
  • @CommonsWare 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. – Smeet May 13 '21 at 14:12
  • @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. – CommonsWare May 13 '21 at 14:33
  • @CommonsWare Yes I check that method - fromTreeUri() but it returns the Uri of rootPath not the child folders. Don't know why. – Smeet May 13 '21 at 15:41
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
}
Pavel Shirokov
  • 432
  • 4
  • 6
  • 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. – Rubén Viguera Jun 04 '21 at 14:11
  • 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-. – Rubén Viguera Jun 04 '21 at 14:15