LIFED
LIFED

Reputation: 467

Show an image in MessagingStyle notification in Android

I'm trying to show an image message notification using MessagingStyle. There is method setData with 2 parameters MIME-type and Uri to set the image to Message object. I have an url of image. Firstly, I download it as bitmap using glide and then save it to Internal storage to be able to get file's Uri. The problem is that notification doesn't show an image (text only).

When I tried to save bitmap to External storage there were not any problems to show an image except we need the WRITE_EXTERNAL_STORAGE permission to save a file. But I don't want to ask users for the permission just to show an image in notification.

Does system have permission to read app's Internal storage? Or where should I save an image without any permissions to be able to show it in notification with Uri?

Set an image to notification:

val message = NotificationCompat.MessagingStyle.Message(messageText, System.currentTimeMillis(), person)
newMessage.originalUrl?.let { imageUrl ->
    val futureTarget = Glide.with(context)
        .asBitmap()
        .load(imageUrl)
        .submit()

    try {
        val bitmap = futureTarget.get()
        val imageUri = FileUtils.getUriFromBitmap(context, bitmap)
        message.setData("image/", imageUri)
    } catch (e: Exception) {
        Log.e(TAG, "Failed to load notification photo")
        e.printStackTrace()
    }

    Glide.with(context).clear(futureTarget)
}
object FileUtils {
    fun getUriFromBitmap(context: Context, bitmap: Bitmap): Uri {
        val file = saveBitmapToInternalStorage(context, bitmap)
        return Uri.fromFile(file)
    }

    private fun saveBitmapToInternalStorage(context: Context, bitmap: Bitmap): File {
        val filename = "${UUID.randomUUID()}.jpg"
        context.openFileOutput(filename, Context.MODE_PRIVATE).use { fos ->
            bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos)
            fos.flush()
            fos.close()
        }
        return File(context.filesDir, filename)
    }
}

Upvotes: 1

Views: 2084

Answers (1)

LIFED
LIFED

Reputation: 467

After days of research I finally did it. Previous answer worked, but not on Android 10 (and above). To make it work on Android 10 we need to use FileProvider. Actually, it works on earlier versions of Android too, so, it's one code for all, nice!

First of all we need to add filepaths.xml to res/xml directory. It defines what directories can be shared with other apps (it gives us ability to get content Uri). Following xml means that we can share files under data/cache folder:

<paths>
    <cache-path
        name="cache"
        path="/" />
</paths>

Next add FileProvider to AndroidManifest.xml and specify our filepaths.xml there:

<manifest>

    <application>

        ...

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.app.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>

    </application>

</manifest>

Awesome part of this solutions is that we don't need to carry of bitmap conversion and file storing. We can just ask Glide to do this for us by simply adding .downloadOnly() to RequestBuilder:

newMessage.originalUrl?.let { imageUrl ->
    try {
        val imageFile = Glide.with(context)
            .downloadOnly()
            .load(imageUrl)
            .submit()
            .get()

        val imageUri = FileUtils.getFileUriFromCache(context, imageFile)
        message.setData("image/", imageUri)
    } catch (e: Exception) {
        Log.e(TAG, "Failed to load notification photo")
        e.printStackTrace()
    }
}

It returns a file stored to data/cache location. All we need now is to get correct content:// (not file:///) Uri that can be shared to notification. And we can do it using FileProvider:

object FileUtils {
    fun getFileUriFromCache(context: Context, file: File): Uri? {
        return try {
            FileProvider.getUriForFile(context, "com.example.app.fileprovider", file)
        } catch (e: IllegalArgumentException) {
            Log.e("File Selector",
                "The selected file can't be shared: $file")
            null
        }
    }
}

That's it!

P.S: Actually, Android 10 (and above) can display 2 images per notification.

PREVIOUS ANSWER: Doesn't work on Android 10 and above

The problem was that file was not being saved. I changed saving location from context.filesDir to context.externalCacheDir and now it works.

Also you don't really need to make a filename unique for 2 reasons:

  1. It's only 1 image message exists per notification (previous images get disappear), and other notifications already show images and they won't disappear;
  2. Since first, why to full storage with useless files.
object FileUtils {
    fun getUriFromBitmap(context: Context, bitmap: Bitmap): Uri {
        val file = saveBitmapToCacheDir(context, bitmap)
        return Uri.fromFile(file)
    }

    private fun saveBitmapToCacheDir(context: Context, bitmap: Bitmap): File {
        val file = File(context.externalCacheDir, "notification_image.jpg")
        val fos = FileOutputStream(file)
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos)
        fos.flush()
        fos.close()
        return file
    }
}

Upvotes: 1

Related Questions