NotificationCompat.Builder assembles a notification for one channel. It needs a small icon (a monochrome vector the status bar tints), a title and text; everything else is optional. A PendingIntent gives it a tap action. Since Android 12 it must declare its mutability, and FLAG_IMMUTABLE is the safe choice; here it reuses the booknest://book/{id} deep link of Navigation Compose, so a tap opens the book itself:
fun showPriceDrop(context: Context, book: Book, oldPrice: Double) {
if (!canPost(context)) {
Log.i("BookNestNotify", "no permission: price drop for ${book.title} not shown")
return
}
val open = Intent(Intent.ACTION_VIEW, "booknest://book/${book.id}".toUri(),
context, MainActivity::class.java) // the deep link of Section 5.21
val tap = PendingIntent.getActivity(context, book.id, open,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val text = "Now $%.2f, was $%.2f. Tap to see the book.".format(book.price, oldPrice)
val notification = NotificationCompat.Builder(context, DEALS)
.setSmallIcon(R.drawable.menu_book_24px) // monochrome status-bar icon
.setContentTitle("${book.title} is on sale")
.setContentText(text)
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
.setContentIntent(tap)
.setAutoCancel(true) // dismiss when tapped
.build()
NotificationManagerCompat.from(context).notify(book.id, notification)
Log.i("BookNestNotify", "posted id=${book.id} on channel $DEALS")
}Posting again with the same id (one per book here) updates the notification instead of stacking another, and cancel(id) removes it. Without the permission the system drops it silently, hence the check and the log.