P.Juni
P.Juni

Reputation: 2485

Wrong image name and type when saving bitmap Android 8

I have a fun that saves bitmap as PNG or JPG (both not working), but seems like using content values not working as expected.

  1. File name is incorrect.
  2. File type is incorrect.

What am I missing ? Works on Android 10, but not working on Android 8

fun Bitmap.save(context: Context) {
    val contentResolver = context.contentResolver

    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png")
        put(MediaStore.MediaColumns.TITLE, "test")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
            put(MediaStore.MediaColumns.IS_PENDING, 1)
        }
    }

    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    val uri = contentResolver.insert(contentUri, contentValues)
    if (uri != null) {
        try {
            contentResolver.openFileDescriptor(uri, "w", null)?.use {
                if (it.fileDescriptor != null) {
                    with(FileOutputStream(it.fileDescriptor)) {
                        compress(
                            Bitmap.CompressFormat.PNG,
                            DEFAULT_IMAGE_QUALITY,
                            this
                        )
                        flush()
                        close()
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentValues.clear()
            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
            contentResolver.update(uri, contentValues, null, null)
        }

        MediaScannerConnection.scanFile(context, arrayOf(uri.toString()), null, null)
    }
    recycle()
}

Actual file name is 1592205828045 (some timestamp)

Actual file type is jpg with 0B - as it was not saved properly ?

Upvotes: 3

Views: 1193

Answers (2)

CanonicalBear
CanonicalBear

Reputation: 397

You will have to maintain 2 different ways of saving images to shared storage. This post covers it quite well. Using Media Store API in older phones results in the problem you have described. Some code sample for you (tested in Android 8, 10, and 11).

Add these to your manifest

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!--  File save functions handles this  -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"
    tools:ignore="ScopedStorage" />

Add a permission check to your app (code not provided)

When you are ready with your bitmap call either of these functions (depending on the SDK version of the phone that the app is currently running on)

    //TODO - bitmap needs null check
      val bitmap = BitmapFactory.decodeFile(bitmapFile.canonicalPath)

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
         {
           saveBitmapPreQ(bitmap)
         } else {
           saveBitmapPostQ(bitmap)
         }

Finally these are the implementations of saveBitmapPreQ and saveBitmapPostQ

@Suppress("DEPRECATION") // Check is preformed on function call
private fun saveBitmapPreQ(thisBitmap: Bitmap){
    Log.d("HOME_4", "in pre Q")
    val pictureDirectory = 
   File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), 
   "MyFolder")

    if (!pictureDirectory.exists()){
        pictureDirectory.mkdir()
    }

    val dateTimeStamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
    val name = "Image_$dateTimeStamp"

    val bitmapFile = File(pictureDirectory, "$name.png")

    try {
        val fileOutputStream = bitmapFile.outputStream()
        thisBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
        fileOutputStream.flush()
        fileOutputStream.close()
    } catch (e: Exception) {
        Log.d("HOME_5", "Pre Q error $e")
    }

}

private fun saveBitmapPostQ(thisBitmap: Bitmap){
    Log.d("HOME_6", "in post Q")
    val dateTimeStamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
    val name = "Image_$dateTimeStamp"
    val relativePath = Environment.DIRECTORY_PICTURES + File.separator + "MyFolder"


    val contentValues = ContentValues().apply {
        put(MediaStore.Images.ImageColumns.DISPLAY_NAME, name)

        put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        put(MediaStore.MediaColumns.TITLE, name)
        put(MediaStore.Images.ImageColumns.RELATIVE_PATH, relativePath)
    }

    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    var outputStream: OutputStream? = null
    var uri: Uri? = null

    try {
        uri = contentResolver.insert(contentUri, contentValues)

        if (uri == null){
            throw IOException("Failed to create new MediaStore record.")
        }

        outputStream = contentResolver.openOutputStream(uri)

        if (outputStream == null){
            throw IOException("Failed to get output stream.")
        }

        if (!thisBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)){
            throw IOException("Failed to save bitmap.")
        }
    } catch (e: IOException){
        if (uri != null)
        {
            contentResolver.delete(uri, null, null)
        }

        throw IOException(e)
    }

    finally {
        outputStream?.close()
    }
}

I have left log messages in there to help you understand the flow. In the saveBitmapPostQ funtions I have taken a few shortcuts. Please read this post under the headding Creating a New File on how you can improve that function further.

Upvotes: 6

Bram Stoker
Bram Stoker

Reputation: 1232

You are creating the file, but you still need to write your Bitmap to it:

fun Bitmap.save(context: Context) {

    ...

    val bitmap = this
    val maxImageQuality = 100

    val uri = contentResolver.insert(contentUri, contentValues)
    if (uri != null) {
        try {
            contentResolver.openFileDescriptor(uri, "w", null)?.use {
                if (it.fileDescriptor != null) {
                    with(FileOutputStream(it.fileDescriptor)) {
                        bitmap.compress(
                            Bitmap.CompressFormat.PNG,
                            maxImageQuality, this
                        )
                        flush()
                        close()
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        // release pending status of the file
        contentValues.clear()
        contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
        contentResolver.update(uri, contentValues, null, null)

        // notify media scanner there's a new picture
        MediaScannerConnection.scanFile(context, arrayOf(uri.toString()), null, null)
    }
    // don't forget to recycle the bitmap when you don't need it any longer
    bitmap.recycle()
}

Upvotes: 1

Related Questions