Android's notification system is rich and customizable. Understanding notification channels, styles, and actions lets you build engaging, well-behaved notifications that users actually want.
Notification Channels: Mandatory on API 26+
Channels let users control notification types independently. Always create all channels in Application.onCreate():
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
createNotificationChannels()
}
private fun createNotificationChannels() {
val manager = getSystemService(NotificationManager::class.java)
val channels = listOf(
NotificationChannel("messages", "Messages", NotificationManager.IMPORTANCE_HIGH).apply {
description = "Direct messages from other users"
enableVibration(true)
enableLights(true)
lightColor = Color.BLUE
setShowBadge(true)
},
NotificationChannel("social", "Social", NotificationManager.IMPORTANCE_DEFAULT).apply {
description = "Likes, comments, and follows"
setShowBadge(true)
},
NotificationChannel("promotions", "Promotions", NotificationManager.IMPORTANCE_LOW).apply {
description = "Deals and promotional offers"
setShowBadge(false)
},
NotificationChannel("reminders", "Reminders", NotificationManager.IMPORTANCE_HIGH).apply {
description = "Event and appointment reminders"
enableVibration(true)
}
)
// Create group (optional: groups multiple channels in settings)
val socialGroup = NotificationChannelGroup("social_group", "Social")
manager.createNotificationChannelGroup(socialGroup)
channels.forEach { channel ->
if (channel.id in listOf("social", "messages")) {
channel.group = "social_group"
}
manager.createNotificationChannel(channel)
}
}
}
BigTextStyle
NotificationCompat.Builder(context, "messages")
.setSmallIcon(R.drawable.ic_message)
.setContentTitle("Alice")
.setContentText("Hey, are you free this weekend?") // short preview
.setStyle(
NotificationCompat.BigTextStyle()
.bigText("Hey, are you free this weekend? We're planning a hike up to the summit and it'd be great if you could join us! Let me know by Thursday.")
.setBigContentTitle("Alice")
.setSummaryText("Direct message")
)
.build()
InboxStyle (Stacked Messages)
NotificationCompat.Builder(context, "messages")
.setSmallIcon(R.drawable.ic_messages)
.setContentTitle("3 new messages")
.setStyle(
NotificationCompat.InboxStyle()
.addLine("Alice: Are you free this weekend?")
.addLine("Bob: Great work on the presentation!")
.addLine("Carol: Call me when you get a chance")
.setBigContentTitle("3 new messages")
.setSummaryText("inbox")
)
.build()
BigPictureStyle
val bitmap = Glide.with(context).asBitmap().load(imageUrl).submit().get()
NotificationCompat.Builder(context, "social")
.setSmallIcon(R.drawable.ic_photo)
.setContentTitle("Alice liked your photo")
.setStyle(
NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null as Bitmap?) // hide large icon when expanded
)
.setLargeIcon(userAvatarBitmap)
.build()
MessagingStyle (Chat Notifications)
val person = Person.Builder()
.setName("Alice")
.setIcon(IconCompat.createWithBitmap(aliceAvatarBitmap))
.build()
val messagingStyle = NotificationCompat.MessagingStyle(me)
.setConversationTitle("Work Group") // null for 1:1 conversations
.addMessage("Hey, meeting at 3pm?", timestamp1, alice)
.addMessage("Sure, conference room B?", timestamp2, bob)
.addMessage("Works for me!", timestamp3, me)
NotificationCompat.Builder(context, "messages")
.setSmallIcon(R.drawable.ic_chat)
.setStyle(messagingStyle)
.setShortcutId("conversation_${conversationId}") // links to app shortcut
.addAction(buildReplyAction(conversationId)) // inline reply
.build()
Inline Reply Action
fun buildReplyAction(conversationId: String): NotificationCompat.Action {
val remoteInput = RemoteInput.Builder("reply_key")
.setLabel("Reply…")
.build()
val replyPendingIntent = PendingIntent.getBroadcast(
context,
conversationId.hashCode(),
Intent(context, ReplyReceiver::class.java).putExtra("conversation_id", conversationId),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
return NotificationCompat.Action.Builder(
R.drawable.ic_reply, "Reply", replyPendingIntent
).addRemoteInput(remoteInput).build()
}
class ReplyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val reply = RemoteInput.getResultsFromIntent(intent)?.getCharSequence("reply_key") ?: return
val conversationId = intent.getStringExtra("conversation_id") ?: return
// Send reply (use WorkManager for the actual network call)
WorkManager.getInstance(context).enqueue(
OneTimeWorkRequestBuilder<SendReplyWorker>()
.setInputData(workDataOf("conversation_id" to conversationId, "reply" to reply.toString()))
.build()
)
}
}
Key Takeaways
| Style | Best for |
|---|---|
BigTextStyle | Long text messages; email previews |
InboxStyle | Multiple items (emails, updates) stacked |
BigPictureStyle | Image previews (photo likes, post previews) |
MessagingStyle | Chat conversations; shows avatar and sender name |
| Inline reply | Chat apps; lets users respond without opening the app |
| Channel groups | Organize related channels in settings UI |