Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MediaScannerConnection.scanFile() returning null uri

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>
like image 261
Daniel Bertoldi Avatar asked Sep 13 '20 10:09

Daniel Bertoldi


2 Answers

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).

like image 161
CommonsWare Avatar answered Nov 17 '22 20:11

CommonsWare


... 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"
}
like image 1
Frank Avatar answered Nov 17 '22 21:11

Frank