I wanna get(read/generate) file Uri
-- with path like below -- by FileProvider
, but don't know how:
val file = File("/external/file/9359") // how do I get its Uri then?
All I know was FileProvider.getUriForFile
method, but it throws exception.
I was trying to mock a download progress -- create a file in Download
folder, and then pass it to ShareSheet
to let user do whatever it want to do.
What I have done:
Download
by using MediaStore
and ContentResolver
.ShareSheet
util function.FileProvider
and filepath.xml
.In my case, I want to share the file via ShareSheet
function, which requires
File
but the usual file.toUri()
will throw exception above SDK 29. Hence I change it into FileProvider.getUriForFile()
as Google-official recommended.
val fileUri: Uri = FileProvider.getUriForFile(context, "my.provider", file)
Will throw exception:
java.lang.IllegalArgumentException: Failed to find configured root that contains /external/file/9359
val fileUri: Uri? = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "test.txt")
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// create file in download directory.
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
}.let { values ->
context.contentResolver.insert(MediaStore.Files.getContentUri("external"), values)
}
if (fileUri == null) return
context.contentResolver.openOutputStream(fileUri)?.use { fileOut ->
fileOut.write("Hello, World!".toByteArray())
}
val file = File(fileUri.path!!) // File("/external/file/9359")
I can assure the code is correct because I can see the correct file in Download
folder.
I have registered provider in AndroidManifest.xml:
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="my.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepath" />
</provider>
</application>
with filepath.xml below:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
</paths>
I've also tried so many path variants, one by one:
<external-files-path name="download" path="Download/" />
<external-files-path name="download" path="." />
<external-path name="download" path="Download/" />
<external-path name="download" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />
I also even registered external storage reading permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
which part did I do wrong?
After posting, I thought the File
initialization might be the problem:
File(fileUri.path!!)
So I changed it to
File(fileUri.toString())
but still not working.
Their content difference is below btw:
fileUri.path -> /external/file/9359
(file.path) -> /external/file/9359
error -> /external/file/9359
fileUri.toString() -> content://media/external/file/9359
(file.path) -> content:/media/external/file/9359
error -> /content:/media/external/file/9359
What I originally wanted to achieve is sending binary data to other app. As Official-documented, it seems only accept nothing but file Uri
.
I'd be appreciate if there's any other way to achieve this, like share the File
directly, etc.
But what I'm wondering now is simple -- How do I make FileProvider
available to get/read/generate file Uri
like /external/file/9359
or so on? This might comes in help not only this case, and seems like a more general/basic knowledge to me.
Content uris does not have file-path or scheme. You should create an input stream and by using this inputstream create a temporary file. You can extract a file-uri or path from this file. Here is a function of mine to create a temporary file from content-uri:
fun createFileFromContentUri(fileUri : Uri) : File{
var fileName : String = ""
fileUri.let { returnUri ->
requireActivity().contentResolver.query(returnUri,null,null,null)
}?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
}
// For extract file mimeType
val fileType: String? = fileUri.let { returnUri ->
requireActivity().contentResolver.getType(returnUri)
}
val iStream : InputStream = requireActivity().contentResolver.openInputStream(fileUri)!!
val outputDir : File = context?.cacheDir!!
val outputFile : File = File(outputDir,fileName)
copyStreamToFile(iStream, outputFile)
iStream.close()
return outputFile
}
fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
inputStream.use { input ->
val outputStream = FileOutputStream(outputFile)
outputStream.use { output ->
val buffer = ByteArray(4 * 1024) // buffer size
while (true) {
val byteCount = input.read(buffer)
if (byteCount < 0) break
output.write(buffer, 0, byteCount)
}
output.flush()
}
}
}
--Edit--
Content Uri has path but not file path. For example;
content Uri path: content://media/external/audio/media/710 ,
file uri path : file:///sdcard/media/audio/ringtones/nevergonnagiveyouup.mp3
By using the function above you can create a temporary file and you can extract uri and path like this:
val tempFile: File = createFileFromContentUri(contentUri)
val filePath = tempFile.path
val fileUri = tempFile.getUri()
After using tempFile delete it to prevent memory issues(it will be deleted because it is written in cache ):
tempFile.delete()
There is no need to edit content uri. Adding scheme to content uri probably not gonna work