MediaScannerConnection.scanFile() returning null uri
Asked Answered
C

2

7

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>
Courtesy answered 13/9, 2020 at 10:44 Comment(14)
If you can save content to file then there is no need to call the mediascanner if you want to share that file. Why would you? You can serve/send the file without. Please elaborate.Babbie
Do away with that intermediate byte array output steam. You can compress the bitmap to the file output stream directly.Babbie
tried adding "file://" + file.absolutePath" like I saw in some other answers but it still didn't work. And why not? You should tell why.Babbie
@Babbie sorry, but I don't quite understand what you mean. I'm new to Android programming. My thought process was to first save the file, and then share it. Looking up on the internet, I found out that you could use the MediaScannerConnection to scan the file and share it with an Intent. If I compress the file output to stream, then I won't be able to save the file?Courtesy
@Babbie because the uri returned in the .scanFile() was, still, null.Courtesy
If you compress the bitmap to fileoutputstream directly then the file is saved.Babbie
You did not tell the full path where you saved the file. You could indeed use the mediascanner to obtain an uri for your file but... it is not the usual way to obtain an uri in order to be able to share. And.. the mediascanner will not scann from all all paths...Babbie
null is an expected value for the Uri -- see the documentation. It means that the scan failed. "I'm using API 21" -- what precisely do you mean by this? Do you mean that you are running the app on an Android 5.0 device?Flagstad
@Babbie I've updated my question with my File Provider code, I hope that helps. I thought the Log output showed the full path.Courtesy
@Flagstad yes, I've read the documentation, but 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. The API 21 was just to have a better idea of what API I am using, but I'm running the app on an Android 10 device.Courtesy
I do not see code where you actually use your FileProvider. Which you should instead of the media scanner.Babbie
and the path is a valid one That path is an app specific path and the media store does not want to know about such paths. Hence the null.Babbie
@Babbie so what could I do in this case? Instead of using Android/data/com.example.kidsdrawingapp/files should I just use .?Courtesy
You should use your FileProvider.Babbie
F
7

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

Flagstad answered 13/9, 2020 at 11:27 Comment(1)
in addition, Will see /storage/emulated/0/Android/data/.nomedia if android do not index the app-specific external storage. ".nomedia" file means ignore this directory and it's subdirectory.Nordine
V
1

... 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"
}
Vaporish answered 17/8, 2021 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.