I'm trying to save a file as PNG of a canvas where the user can draw something and then call an Intent.ACTION_SEND
so that the user can share it's drawing with other apps.
The code is able to save the file without any problems, but when I try to use the MediaScannerConnection.scanFile()
, the Uri
returned by the function is null
. I'm using the absolute path of the file created, so I can't understand why this is happening.
My class, called BitmapAsyncTask
inherits from AsyncTask
(yes, I know it's deprecated). Here's the important code:
Writing the file to memory:
override fun doInBackground(vararg p0: Any?): String {
var result = ""
try {
val bytes = ByteArrayOutputStream()
mBitmap.compress(Bitmap.CompressFormat.PNG, 95, bytes)
val file = File(externalCacheDir!!.absoluteFile.toString()
+ File.separator + "KidsDrawingApp_"
+ System.currentTimeMillis() / 1000 + ".png")
val fileOutput = FileOutputStream(file)
fileOutput.write(bytes.toByteArray())
fileOutput.close()
result = file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
}
Log.d("File", result)
return result
}
** The mBitmap
variable is just the Bitmap generated from the canvas.
Here, the Log.d
returns, for instance:
D/File: /storage/emulated/0/Android/data/com.example.kidsdrawingapp/cache/KidsDrawingApp_1599992654.png
I can access the file just fine if I open the Files
app and search for it.
But when I run the MediaScannerConnection
on onPostExecute()
, the function doesn't return an uri based on the absolute path at all. Here's the code:
MediaScannerConnection.scanFile(this@MainActivity, arrayOf(result), null) {
path, uri -> val shareIntent = Intent()
Log.d("Path", path)
Log.d("Uri", uri.toString())
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
shareIntent.type = "image/png"
startActivity(
Intent.createChooser(
shareIntent, "Share image"
)
)
}
Once again, the Log.d("Path", path)
returns the same file as the previous Log.d()
, but when I try to convert the Uri
to string, it crashes because it's null.
I tried adding "file://" + file.absolutePath"
like I saw in some other answers but it still didn't work, the uri
returned by the scanFile()
was still null.
I'm using API 21.
File Provider Code
AndroidManifest.xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.kidsdrawingapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/path" />
</provider>
@xml/path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="captured" path="Android/data/com.example.kidsdrawingapp/files" />
</paths>
I can't seem to figure out why it can't return a valid uri if the file is being saved in the phone and the path is a valid one
It is valid. However, it is not indexable by MediaStore
on Android 10 and higher. MediaStore
will no longer index files in app-specific external storage, such as getExternalFilesDir()
, which is what you are using.
If your objective is to have the image be usable by every app on the device, then getting indexed by MediaStore
is fine. On Android 10+, you can insert()
into the MediaStore
and use the resulting Uri
for writing out your content. See this sample app for a demonstration, though in my case I am writing out a video, not a PNG.
If, instead, all you want to do is share this content, then do not use MediaScannerConnection
. Instead, use FileProvider
. See the documentation and this sample app (though in my case I am sharing a PDF, not a PNG).
... in case the above solution was not fully clear to everyone - here's how I applied the suggested fix to the reported file sharing issue within the tutorial exercise "Kids Drawing App" (from "The Complete Android 10 & Kotlin Development Masterclass" at Udemy):
// offer to share content
MediaScannerConnection.scanFile(
this@MainActivity,
arrayOf(result),
null
) { path, _ ->
// Use the FileProvider to get a content URI
val requestFile = File(path)
val fileUri: Uri? = try {
FileProvider.getUriForFile(
this@MainActivity,
AUTHORITY,
requestFile)
} catch (e: IllegalArgumentException) {
Log.e("File Selector",
"The selected file can't be shared: $requestFile")
null
}
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "image/png"
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
startActivity(
Intent.createChooser(
shareIntent, "Share"
)
)
}
... where I added the following AUTHORITY definition:
// static variables
companion object {
private const val STORAGE_PERMISSION_CODE = 1
private const val GALLERY = 2
private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.fileprovider"
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With