Android - how to save document files to Documents folder with SAF (Java)

190 views Asked by At

I am having trouble with an issue saving a PDF and CSV file to the Documents folder on Android. On two devices it seems to work (Samsung Galaxy Tab S8+ and Teclast M40 Plus), but on one device (Lenovo M10 Plus FHD 2nd Gen) it does not.

I tried various solutions and libraries and none seem to work. None of them seem to be able to get the file path from the URI.

This is my attempt usng the pickiT library;

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode != RESULT_OK) {
        Log.d("GraphActivity", "Result OK");
        return;
    }
    Uri treeUri = data.getData();
    DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
    grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    Uri docUri = DocumentsContract.buildDocumentUriUsingTree(treeUri,
            DocumentsContract.getTreeDocumentId(treeUri));
    String path = null;
    int version = Build.VERSION.SDK_INT;
        pickiT.getPath(docUri, version);
        // Uri is from unknown provider
}

@Override
public void PickiTonCompleteListener(String path, boolean wasDriveFile, boolean wasUnknownProvider, boolean wasSuccessful, String Reason) {
    Log.d("GraphActivity", "Path is not null");
    folder = new File(path);

    String timestamp = new SimpleDateFormat("MM_dd_yy_hh.mm", Locale.getDefault()).format(new Date());

    baseFilename = name + " " + timestamp;
    filename = "/" + folder.toString() + "/" + baseFilename + ".csv";

    if (filename == null) {
        Log.d("GraphAcitivty", "Filename is null");
    } else {
        Log.d("GraphAcitivty", "Filename is not null");
    }

    showSaveDialog();
}

Apparently PickiTonCompleteListener isn't even being called based on my print statements. My activity does implement PickiTCallbacks but for some reason the callback isn't working.

I wonder, is there any solution out there that works?

2

There are 2 answers

0
Mohmmaed-Amleh On

when u want to insert a new document, the preferred way is to Use DownloadsFolder, on api Q+(api 29) there's a content provider that makes it easy .

this code will be compatible with all android version.

Hint below android 10 u can use Document Folder for this solution but the recommended is to use Downloads Folder.

this is kotlin code convert it to java

if this helped pls upvote

fun example() {
      var filename = "yourFileName_${System.currentTimeMillis()}.pdf"

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val uri = MediaStoreUtils.createDownloadUri(context, filename)

              if(uri == null){
                 return
               }

           try {
                context.contentResolver.openOutputStream(uri, "w")?.use { outputStream ->
                   //write your file InputStream to the OutputStream
                }

            } catch (e: IOException) {
                Log.e(TAG, e.printStackTrace().toString())
            }
        } else {
            val downloadsFolder =
                Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)

            //re-generate the filename until we can confirm its uniqueness
            while (File(downloadsFolder, filename).exists()) {
                filename = generateFilename(type.extension)
            }

            val file = File(downloadsFolder, filename)

            try {
                file.outputStream().use { outputStream ->
                   //write your file InputStream to the OutputStream
                }
            } catch (e: IOException) {
                Log.e(TAG, e.printStackTrace().toString())
            }
        }
}


@RequiresApi(Build.VERSION_CODES.Q)
fun createDownloadUri(context: Context, filename: String): Uri? {
    val downloadsCollection =
        MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

        val newFileValues = ContentValues().apply {
            put(MediaStore.Downloads.DISPLAY_NAME, filename)
        }

        // is better to execute off the main thread
        // run on Background Thread (IO)
        return context.contentResolver.insert(downloadsCollection, newFileValues)
}
0
Pink Jazz On

I figured it out.

Instead of using the File class I can use DocumentFile and do this:

final DocumentFile csvFile = pickedDir.createFile("text/csv", baseFileName);
if (csvFile != null) {
    out = context.getContentResolver().openOutputStream(csvFile.getUri());
}

Did not have to use the PickiT library to do this. For the PDF file, I use MIME type application/pdf.