Reputation: 467
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
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:
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