Notifications serve as a primary mechanism for Android applications to display messages outside the standard user interface. They typically appear in the status bar at the top of the screen, providing users with reminders, communication from others, or timely information from the app. Users can interact with notifications by tapping them to open the app or performing actions directly within the notification interface.
When a notification is issued, it appears as an icon in the status bar. Users can swipe down to access the notification drawer, revealing detailed information. The system handles the visual representation, determining how notifications appear in different contexts, such as lock screens or wearable devices.
Platform Version Adaptatiosn
Android has evolved significantly regarding notification handling. Developers must account for these changes to ensure compatibility.
- Android 5.0 (API 21): Introduced lock screen notifications and "Heads-up" notifications (floating banners). Added
setVisibility()to control lock screen visibility andsetPriority()to influence interruption level. - Android 7.0 (API 24): Enhanced notification templates and introduced Notification Groups (bundling). It also enabled direct reply functionality within notifications.
- Android 8.0 (API 26): Introduced Notification Channels. Developers must now categorize notifications into channels, allowing users to manage preferences per channel rather than per app. It also introduced notification badges on app icons.
- Android 12 (API 31): Enforced strict requirements on
PendingIntentmutability. Developers must explicitly specifyFLAG_IMMUTABLEorFLAG_MUTABLE. - Android 13 (API 33): Introduced the
POST_NOTIFICATIONSruntime permission. Apps must request user permission to display non-foreground service notifications. - Android 14 (API 34): Restricted full-screen intent notifications to specific use cases like alarms and calls.
Anatomy of a Notification
The visual layout is determined by system templates. A standard notification consists of the following key elements:
- Small Icon (Required): Set via
setSmallIcon(). Represents the app in the status bar. - App Name: Provided by the system.
- Timestamp: Provided by the system, customizable via
setWhen(). - Large Icon: Optional, often used for contact photos. Set via
setLargeIcon(). - Title: Set via
setContentTitle(). - Text: Set via
setContentText().
Importance and Priority
Starting from Android 8.0 (API 26), the "importance" of a notification is determined by the Notification Channel it belongs to. For older versions, priority is set on the builder itself. Importance levels dictate how intrusive the notification is:
| Level | Description |
|---|---|
| IMPORTANCE_HIGH | Makes a sound and appears as a heads-up notification. Use for urgent real-time communication. |
| IMPORTANCE_DEFAULT | Makes a sound but does not visually intrude on the screen. |
| IMPORTANCE_LOW | No sound. Shows in the shade but not on the status bar (though the icon appears). |
| IMPORTANCE_MIN | No sound and no status bar icon. Only visible in the full notification shade. |
Implementation Steps
The core classes involved are NotificationManager, NotificationChannel (API 26+), and NotificationCompat.Builder.
- Create Channel: For Android 8.0 and above, create a channel to group notifications.
- Build Notification: Use
NotificationCompat.Builderfor backward compatibility. - Configure Content: Set icons, title, text, and tap intent.
- Notify: Use
NotificationManager.notify()to post the notification.
Basic Notification Example
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channelId = "primary_channel_id"
// 1. Create Channel (Required for API 26+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"General Updates",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "Channel for general app notifications"
}
manager.createNotificationChannel(channel)
}
// 2. Define Tap Action (PendingIntent)
val intent = Intent(this, DetailActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
// 3. Build Notification
val builder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("New Alert")
.setContentText("This is a standard notification message.")
.setPriority(NotificationCompat.PRIORITY_DEFAULT) // Legacy support
.setContentIntent(pendingIntent)
.setAutoCancel(true) // Dismiss on tap
// 4. Issue Notification
manager.notify(101, builder.build())
PendingIntent Mutability
Starting from Android 12 (API 31), creating a PendingIntent requires specifying mutability flags. If omitted, the system throws an exception.
- FLAG_IMMUTABLE: Recommended for most cases. The wrapped Intent cannot be modified by the receiver.
- FLAG_MUTABLE: Required if the Intent contents need to be updated (e.g., adding extras in a notification reply action).
Advanced Styles
The setStyle() method alows the use of rich notification templates.
Progress Bar Notification
Useful for ongoing operations like file downloads. The system handles the visual update; you simply update the same notification ID with new progress values.
val builder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_download)
.setContentTitle("Downloading File")
.setContentText("Download in progress")
.setOngoing(true) // Prevents swiping away
.setProgress(100, 0, false)
manager.notify(notificationId, builder.build())
// Inside your download loop
Thread {
for (progress in 0..100) {
Thread.sleep(100) // Simulate work
builder.setProgress(100, progress, false)
manager.notify(notificationId, builder.build())
}
// Completion
builder.setContentText("Download complete")
.setProgress(0, 0, false)
.setOngoing(false)
manager.notify(notificationId, builder.build())
}.start()
Big Picture Style
Displays a large image when the notification is expanded.
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image)
val style = NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null) // Optional: hide large icon when expanded
val builder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Image Received")
.setContentText("Expand to view image")
.setStyle(style)
.setLargeIcon(bitmap)
Custom Layouts
For unique UI requirements, you can define a custom layout using RemoteViews. Unlike standard layouts, RemoteViews describe a view hierarchy that runs in a remote process (the system UI). Consequently, event handling relies heavily on PendingIntent.
// Define RemoteViews
val remoteViews = RemoteViews(packageName, R.layout.layout_custom_notification)
// Set text and images
remoteViews.setTextViewText(R.id.txt_title, "Custom Title")
remoteViews.setImageViewResource(R.id.img_icon, R.drawable.ic_music)
// Handle Button Clicks via BroadcastReceiver
val playIntent = Intent("ACTION_PLAY")
val playPendingIntent = PendingIntent.getBroadcast(
this, 0, playIntent, PendingIntent.FLAG_IMMUTABLE
)
remoteViews.setOnClickPendingIntent(R.id.btn_play, playPendingIntent)
// Apply to Notification
val customNotification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(remoteViews)
.build()
manager.notify(200, customNotification)
When using custom layouts, ensure they adapt to different screen sizes and orientations. The height constraint are strict: collapsed views are limited to approximately 48dp, while expanded views can reach roughly 256dp.